├── tests
├── Protos
│ └── .gitignore
├── Fixtures
│ ├── compiler
│ │ └── generator-request-simple.bin
│ ├── Service
│ │ └── SearchService.tpl
│ ├── Options
│ │ ├── ParentMessage
│ │ │ └── InnerMessage
│ │ │ │ └── InnerMessageEnum.tpl
│ │ └── SimpleMessage.tpl
│ ├── Person
│ │ └── PhoneType.tpl
│ └── Extension
│ │ └── Extension.tpl
├── Resources
│ ├── proto2
│ │ ├── repeated.proto
│ │ ├── simple.proto
│ │ ├── addressbook.proto
│ │ └── options.proto
│ └── proto3
│ │ └── route_guide.proto
├── travis
│ └── install-protobuf.sh
├── OptionsTest.php
├── Descriptor
│ ├── ToStreamTest.php
│ ├── MessageDescriptorTest.php
│ ├── MergeTest.php
│ ├── ClearTest.php
│ └── SerializedSizeTest.php
├── Generator
│ └── Message
│ │ ├── SerializedSizeGeneratorTest.php
│ │ ├── WriteToGeneratorTest.php
│ │ ├── ReadFromGeneratorTest.php
│ │ └── SerializedSizeFieldStatementGeneratorTest.php
├── Command
│ ├── ApplicationTest.php
│ ├── PluginCommandTest.php
│ └── GenerateCommandTest.php
├── CompilerTest.php
├── EntityTest.php
└── EntityBuilderTest.php
├── src
├── Generator
│ ├── EntityVisitor.php
│ ├── GeneratorVisitor.php
│ ├── Message
│ │ ├── ClearGenerator.php
│ │ ├── FromStreamGenerator.php
│ │ ├── ToStreamGenerator.php
│ │ ├── MergeGenerator.php
│ │ ├── ConstructGenerator.php
│ │ ├── SerializedSizeGenerator.php
│ │ ├── SerializedSizeFieldStatementGenerator.php
│ │ ├── WriteToGenerator.php
│ │ ├── DescriptorGenerator.php
│ │ ├── ReadFromGenerator.php
│ │ ├── WriteFieldStatementGenerator.php
│ │ ├── FromArrayGenerator.php
│ │ └── ReadFieldStatementGenerator.php
│ ├── MessageGenerator.php
│ ├── ServiceGenerator.php
│ ├── EnumGenerator.php
│ └── ExtensionGenerator.php
├── Options.php
├── Context.php
├── Generator.php
├── Command
│ ├── Application.php
│ ├── PluginCommand.php
│ └── GenerateCommand.php
├── Compiler.php
├── Entity.php
└── Protoc
│ └── ProcessBuilder.php
├── ruleset.xml
├── .travis.yml
├── .gitignore
├── bin
└── protobuf
├── LICENSE
├── phpunit.xml.dist
├── composer.json
├── Makefile
└── README.md
/tests/Protos/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/tests/Fixtures/compiler/generator-request-simple.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/protobuf-php/protobuf-plugin/HEAD/tests/Fixtures/compiler/generator-request-simple.bin
--------------------------------------------------------------------------------
/src/Generator/EntityVisitor.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | interface EntityVisitor
13 | {
14 | /**
15 | * @param \Protobuf\Compiler\Entity $entity
16 | */
17 | public function visit(Entity $entity);
18 | }
19 |
--------------------------------------------------------------------------------
/ruleset.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Coding Standard
4 |
5 | */Resources/*
6 |
7 |
8 |
9 |
10 |
11 |
12 | 0
13 |
14 |
15 |
--------------------------------------------------------------------------------
/tests/Resources/proto2/repeated.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 |
3 | package ProtobufCompilerTest.Protos;
4 |
5 | message Repeated {
6 |
7 | message Nested {
8 | optional int32 id = 1;
9 | }
10 |
11 | enum Enum {
12 | FOO = 0;
13 | BAR = 1;
14 | }
15 |
16 | repeated string string = 1;
17 | repeated int32 int = 2;
18 | repeated Nested nested = 3;
19 | repeated int32 packed = 4 [packed=true];
20 | repeated bytes bytes = 5;
21 | repeated Enum enum = 6;
22 | }
23 |
--------------------------------------------------------------------------------
/src/Generator/GeneratorVisitor.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | interface GeneratorVisitor
14 | {
15 | /**
16 | * @param \Protobuf\Compiler\Entity $entity
17 | * @param \Zend\Code\Generator\GeneratorInterface $class
18 | */
19 | public function visit(Entity $entity, GeneratorInterface $class);
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Fixtures/Service/SearchService.tpl:
--------------------------------------------------------------------------------
1 | run();
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
15 | ./tests
16 | ./tests/Resources/*
17 |
18 |
19 |
20 |
21 |
22 | benchmark
23 | deprecated
24 |
25 |
26 |
27 |
28 |
29 | ./vendor/*
30 |
31 |
32 | ./src
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/tests/OptionsTest.php:
--------------------------------------------------------------------------------
1 | 1,
13 | 'verbose' => 1,
14 | 'psr4' => ['MyPackage'],
15 | ]);
16 |
17 | $options2 = Options::fromArray([
18 | 'generate-imported' => 0,
19 | 'verbose' => 0
20 | ]);
21 |
22 | $this->assertTrue($options1->getVerbose());
23 | $this->assertTrue($options1->getGenerateImported());
24 | $this->assertEquals(['MyPackage'], $options1->getPsr4());
25 |
26 | $this->assertFalse($options2->getVerbose());
27 | $this->assertFalse($options2->getGenerateImported());
28 | }
29 |
30 | public function testDefaults()
31 | {
32 | $options = Options::fromArray([]);
33 |
34 | $this->assertFalse($options->getVerbose());
35 | $this->assertFalse($options->getGenerateImported());
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/Fixtures/Options/ParentMessage/InnerMessage/InnerMessageEnum.tpl:
--------------------------------------------------------------------------------
1 | markTestIncompleteIfProtoClassNotFound();
20 |
21 | parent::setUp();
22 | }
23 |
24 | public function testAddressBookToStream()
25 | {
26 | $config = $this->getMock(Configuration::CLASS);
27 | $stream = $this->getMock(Stream::CLASS, [], [], '', false);
28 | $context = $this->getMock(WriteContext::CLASS, [], [], '', false);
29 | $message = $this->getMockBuilder(AddressBook::CLASS)
30 | ->setMethods(['writeTo'])
31 | ->getMock();
32 |
33 | $stream->expects($this->once())
34 | ->method('seek')
35 | ->with($this->equalTo(0));
36 |
37 | $config->expects($this->once())
38 | ->method('createWriteContext')
39 | ->willReturn($context);
40 |
41 | $context->expects($this->once())
42 | ->method('getStream')
43 | ->willReturn($stream);
44 |
45 | $message->expects($this->once())
46 | ->method('writeTo')
47 | ->with($this->equalTo($context));
48 |
49 | $this->assertSame($stream, $message->toStream($config));
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Options.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class Options
11 | {
12 | /**
13 | * @var string[]
14 | */
15 | protected $psr4;
16 |
17 | /**
18 | * @var bool
19 | */
20 | protected $verbose = false;
21 |
22 | /**
23 | * @var bool
24 | */
25 | protected $generateImported = false;
26 |
27 | /**
28 | * @return string
29 | */
30 | public function getVerbose()
31 | {
32 | return $this->verbose;
33 | }
34 |
35 | /**
36 | * @return bool
37 | */
38 | public function getGenerateImported()
39 | {
40 | return $this->generateImported;
41 | }
42 |
43 | /**
44 | * @return array
45 | */
46 | public function getPsr4()
47 | {
48 | return $this->psr4;
49 | }
50 |
51 | /**
52 | * @param array $params
53 | *
54 | * @return \Protobuf\Compiler\Generator\Options
55 | */
56 | public static function fromArray(array $params)
57 | {
58 | $options = new Options();
59 |
60 | if (isset($params['verbose'])) {
61 | $options->verbose = (bool) $params['verbose'];
62 | }
63 |
64 | if (isset($params['generate-imported'])) {
65 | $options->generateImported = (bool) $params['generate-imported'];
66 | }
67 |
68 | if (isset($params['psr4'])) {
69 | $options->psr4 = $params['psr4'];
70 | }
71 |
72 | return $options;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/tests/Generator/Message/SerializedSizeGeneratorTest.php:
--------------------------------------------------------------------------------
1 | createContext([
17 | [
18 | 'name' => 'simple.proto',
19 | 'package' => 'ProtobufCompilerTest.Protos',
20 | 'values' => [
21 | 'messages' => [
22 | [
23 | 'name' => 'Simple',
24 | 'fields' => [
25 | 1 => ['lines', Field::TYPE_INT32, Field::LABEL_REPEATED]
26 | ]
27 | ]
28 | ]
29 | ]
30 | ]
31 | ]);
32 |
33 | $entity = $context->getEntity('ProtobufCompilerTest.Protos.Simple');
34 | $generator = new SerializedSizeGenerator($context);
35 | $descritor = $entity->getDescriptor();
36 | $field = $descritor->getFieldList()[0];
37 |
38 | $actual = $this->invokeMethod($generator, 'generateBody', [$entity]);
39 | $expected = <<<'CODE'
40 | $calculator = $context->getSizeCalculator();
41 | $size = 0;
42 |
43 | if ($this->lines !== null) {
44 | foreach ($this->lines as $val) {
45 | $size += 1;
46 | $size += $calculator->computeVarintSize($val);
47 | }
48 | }
49 |
50 | if ($this->extensions !== null) {
51 | $size += $this->extensions->serializedSize($context);
52 | }
53 |
54 | return $size;
55 | CODE;
56 |
57 | $this->assertEquals($expected, implode(PHP_EOL, $actual));
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Generator/Message/ClearGenerator.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | class ClearGenerator extends BaseGenerator implements GeneratorVisitor
18 | {
19 | /**
20 | * {@inheritdoc}
21 | */
22 | public function visit(Entity $entity, GeneratorInterface $class)
23 | {
24 | $class->addMethodFromGenerator($this->generateClearMethod($entity));
25 | }
26 |
27 | /**
28 | * @param \Protobuf\Compiler\Entity $entity
29 | *
30 | * @return \Zend\Code\Generator\GeneratorInterface
31 | */
32 | protected function generateClearMethod(Entity $entity)
33 | {
34 | $lines = $this->generateBody($entity);
35 | $body = implode(PHP_EOL, $lines);
36 | $method = MethodGenerator::fromArray([
37 | 'name' => 'clear',
38 | 'body' => $body,
39 | 'docblock' => [
40 | 'shortDescription' => "{@inheritdoc}"
41 | ]
42 | ]);
43 |
44 | return $method;
45 | }
46 |
47 | /**
48 | * @param \Protobuf\Compiler\Entity $entity
49 | *
50 | * @return string[]
51 | */
52 | public function generateBody(Entity $entity)
53 | {
54 | $body = [];
55 | $descriptor = $entity->getDescriptor();
56 | $fields = $descriptor->getFieldList() ?: [];
57 |
58 | foreach ($fields as $field) {
59 | $name = $field->getName();
60 | $value = $this->getDefaultFieldValue($field);
61 |
62 | $body[] = sprintf('$this->%s = %s;', $name, $value);
63 | }
64 |
65 | return $body;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/tests/Command/ApplicationTest.php:
--------------------------------------------------------------------------------
1 | input = $this->getMock('Symfony\Component\Console\Input\InputInterface');
30 |
31 | $this->generateCommand = $this->getMockBuilder(GenerateCommand::CLASS)
32 | ->disableOriginalConstructor()
33 | ->getMock();
34 |
35 | $this->pluginCommand = $this->getMockBuilder(PluginCommand::CLASS)
36 | ->disableOriginalConstructor()
37 | ->getMock();
38 | }
39 |
40 | public function testSingleCommandApplication()
41 | {
42 | $this->generateCommand->expects($this->once())
43 | ->method('getName')
44 | ->willReturn('my-command');
45 |
46 | $application = new Application($this->generateCommand, $this->pluginCommand);
47 | $definition = $this->invokeMethod($application, 'getDefinition');
48 | $defaultCommands = $this->invokeMethod($application, 'getDefaultCommands');
49 | $commandName = $this->invokeMethod($application, 'getCommandName', [$this->input]);
50 |
51 | $this->assertEquals('my-command', $commandName);
52 | $this->assertEquals([], $definition->getArguments());
53 | $this->assertSame($this->generateCommand, $defaultCommands[2]);
54 | $this->assertSame($this->pluginCommand, $defaultCommands[3]);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tests/Generator/Message/WriteToGeneratorTest.php:
--------------------------------------------------------------------------------
1 | createContext([
17 | [
18 | 'name' => 'simple.proto',
19 | 'package' => 'ProtobufCompilerTest.Protos',
20 | 'values' => [
21 | 'messages' => [
22 | [
23 | 'name' => 'Simple',
24 | 'fields' => [
25 | 1 => ['lines', Field::TYPE_INT32, Field::LABEL_REPEATED]
26 | ]
27 | ]
28 | ]
29 | ]
30 | ]
31 | ]);
32 |
33 | $entity = $context->getEntity('ProtobufCompilerTest.Protos.Simple');
34 | $generator = new WriteToGenerator($context);
35 | $descritor = $entity->getDescriptor();
36 | $field = $descritor->getFieldList()[0];
37 |
38 | $actual = $this->invokeMethod($generator, 'generateBody', [$entity]);
39 | $expected = <<<'CODE'
40 | $stream = $context->getStream();
41 | $writer = $context->getWriter();
42 | $sizeContext = $context->getComputeSizeContext();
43 |
44 | if ($this->lines !== null) {
45 | foreach ($this->lines as $val) {
46 | $writer->writeVarint($stream, 8);
47 | $writer->writeVarint($stream, $val);
48 | }
49 | }
50 |
51 | if ($this->extensions !== null) {
52 | $this->extensions->writeTo($context);
53 | }
54 |
55 | return $stream;
56 | CODE;
57 |
58 | $this->assertEquals($expected, implode(PHP_EOL, $actual));
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "protobuf-php/protobuf-plugin",
3 | "description" : "PHP Code generator plugin from Google's Protocol Buffers",
4 | "keywords" : ["protobuf", "protocol buffer", "serializing"],
5 | "homepage" : "https://github.com/protobuf-php/protobuf-plugin",
6 | "type" : "library",
7 | "license" : "MIT",
8 | "prefer-stable" : true,
9 | "authors" : [
10 | {
11 | "name" : "Fabio B. Silva",
12 | "email" : "fabio.bat.silva@gmail.com",
13 | "homepage": "https://github.com/FabioBatSilva"
14 | }
15 | ],
16 | "bin" : ["bin/protobuf"],
17 | "require" : {
18 | "php" : ">=5.5.0",
19 | "protobuf-php/protobuf" : ">=0.1",
20 | "protobuf-php/google-protobuf-proto" : ">=0.1",
21 | "zendframework/zend-code" : "^2.6",
22 | "psr/log" : "^1.0",
23 | "symfony/console" : "^2.5|^3.0",
24 | "symfony/process" : "^2.5|^3.0",
25 | "doctrine/inflector" : "^1.0"
26 | },
27 | "require-dev": {
28 | "phpunit/phpunit" : "~4",
29 | "mikey179/vfsstream" : "~1.6",
30 | "instaclick/coding-standard" : "~1.1",
31 | "squizlabs/php_codesniffer" : "~1.5",
32 | "satooshi/php-coveralls" : "~0.6",
33 | "php-mock/php-mock-phpunit" : "^0.2",
34 | "instaclick/object-calisthenics-sniffs" : "dev-master",
35 | "instaclick/symfony2-coding-standard" : "dev-remaster"
36 | },
37 | "autoload" : {
38 | "psr-4": {
39 | "Protobuf\\Compiler\\": "src/"
40 | }
41 | },
42 | "autoload-dev": {
43 | "psr-4": {
44 | "ProtobufCompilerTest\\": "tests"
45 | }
46 | },
47 | "extra": {
48 | "branch-alias": {
49 | "dev-master": "0.1.x-dev"
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Generator/Message/FromStreamGenerator.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | class FromStreamGenerator extends BaseGenerator implements GeneratorVisitor
21 | {
22 | /**
23 | * {@inheritdoc}
24 | */
25 | public function visit(Entity $entity, GeneratorInterface $class)
26 | {
27 | $class->addMethodFromGenerator($this->generateMethod($entity));
28 | }
29 |
30 | /**
31 | * @param \Protobuf\Compiler\Entity $entity
32 | *
33 | * @return string
34 | */
35 | protected function generateMethod(Entity $entity)
36 | {
37 | $lines = $this->generateBody($entity);
38 | $body = implode(PHP_EOL, $lines);
39 | $method = MethodGenerator::fromArray([
40 | 'name' => 'fromStream',
41 | 'body' => $body,
42 | 'static' => true,
43 | 'parameters' => [
44 | [
45 | 'name' => 'stream',
46 | 'type' => 'mixed',
47 | ],
48 | [
49 | 'name' => 'configuration',
50 | 'type' => '\Protobuf\Configuration',
51 | 'defaultValue' => null
52 | ]
53 | ],
54 | 'docblock' => [
55 | 'shortDescription' => "{@inheritdoc}"
56 | ]
57 | ]);
58 |
59 | return $method;
60 | }
61 |
62 | /**
63 | * @return string[]
64 | */
65 | public function generateBody()
66 | {
67 | return [
68 | 'return new self($stream, $configuration);'
69 | ];
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # vim: ts=4:sw=4:noexpandtab!:
2 |
3 | BASEDIR := $(shell pwd)
4 | COMPOSER := $(shell which composer)
5 | PROTOC_VERSION := $(shell protoc --version | grep -oEi '([0-9]).*' | cut -d '.' -f 1)
6 |
7 | help:
8 | @echo "---------------------------------------------"
9 | @echo "List of available targets:"
10 | @echo " composer-install - Installs composer dependencies."
11 | @echo " phpcs - Runs PHP Code Sniffer."
12 | @echo " phpunit - Runs tests."
13 | @echo " phpunit-coverage-clover - Runs tests to genereate coverage clover."
14 | @echo " phpunit-coverage-html - Runs tests to genereate coverage html."
15 | @echo " help - Shows this dialog."
16 | @exit 0
17 |
18 | all: install phpunit
19 |
20 | install: composer-install proto-generate
21 |
22 | test: proto-generate phpcs phpunit
23 |
24 | composer-install:
25 | ifdef COMPOSER
26 | php $(COMPOSER) install --prefer-source --no-interaction;
27 | else
28 | @echo "Composer not found !!"
29 | @echo
30 | @echo "curl -sS https://getcomposer.org/installer | php"
31 | @echo "mv composer.phar /usr/local/bin/composer"
32 | endif
33 |
34 | proto-clean:
35 | rm -rf $(BASEDIR)/tests/Protos/*;
36 |
37 | proto-generate: proto-clean
38 | ifeq ($(PROTOC_VERSION), 3)
39 | php $(BASEDIR)/bin/protobuf --include-descriptors \
40 | --psr4 ProtobufCompilerTest\\Protos \
41 | -o $(BASEDIR)/tests/Protos \
42 | -i $(BASEDIR)/tests/Resources \
43 | $(BASEDIR)/tests/Resources/proto3/*.proto
44 | endif
45 | php $(BASEDIR)/bin/protobuf --include-descriptors \
46 | --psr4 ProtobufCompilerTest\\Protos \
47 | -o $(BASEDIR)/tests/Protos \
48 | -i $(BASEDIR)/tests/Resources \
49 | $(BASEDIR)/tests/Resources/proto2/*.proto
50 |
51 | phpunit:
52 | php $(BASEDIR)/vendor/bin/phpunit -v;
53 |
54 | phpunit-coverage-clover:
55 | php $(BASEDIR)/vendor/bin/phpunit -v --coverage-clover ./build/logs/clover.xml;
56 |
57 | phpunit-coverage-html:
58 | php $(BASEDIR)/vendor/bin/phpunit -v --coverage-html ./build/coverage;
59 |
60 | phpcs:
61 | php $(BASEDIR)/vendor/bin/phpcs -p --extensions=php --standard=ruleset.xml src;
62 |
63 | .PHONY: composer-install phpunit phpcs help
--------------------------------------------------------------------------------
/src/Context.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class Context
13 | {
14 | /**
15 | * @var \Protobuf\Entity[]
16 | */
17 | protected $entities;
18 |
19 | /**
20 | * @var \Protobuf\Compiler\Options
21 | */
22 | protected $options;
23 |
24 | /**
25 | * @var \Protobuf\Configuration
26 | */
27 | protected $configuration;
28 |
29 | /**
30 | * @param array $entities
31 | * @param \Protobuf\Compiler\Options $options
32 | * @param \Protobuf\Configuration $config
33 | */
34 | public function __construct(array $entities, Options $options, Configuration $config)
35 | {
36 | $this->configuration = $config;
37 | $this->options = $options;
38 | $this->entities = $entities;
39 | }
40 |
41 | /**
42 | * @return bool
43 | */
44 | public function hasProtobufExtension()
45 | {
46 | foreach ($this->entities as $entity) {
47 | if ($entity->isProtobufExtension()) {
48 | return true;
49 | }
50 | }
51 |
52 | return false;
53 | }
54 |
55 | /**
56 | * @return \Protobuf\Compiler\Options
57 | */
58 | public function getOptions()
59 | {
60 | return $this->options;
61 | }
62 |
63 | /**
64 | * @return array
65 | */
66 | public function getEntities()
67 | {
68 | return $this->entities;
69 | }
70 |
71 | /**
72 | * @return \Protobuf\Configuration
73 | */
74 | public function getConfiguration()
75 | {
76 | return $this->configuration;
77 | }
78 |
79 | /**
80 | * @param string $fqcn
81 | *
82 | * @return \Protobuf\Entity
83 | */
84 | public function getEntity($fqcn)
85 | {
86 | $class = trim($fqcn, '.');
87 |
88 | if ( ! isset($this->entities[$class])) {
89 | throw new \LogicException("Unable to find class : $class");
90 | }
91 |
92 | return $this->entities[$class];
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/Generator/Message/ToStreamGenerator.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | class ToStreamGenerator extends BaseGenerator implements GeneratorVisitor
21 | {
22 | /**
23 | * {@inheritdoc}
24 | */
25 | public function visit(Entity $entity, GeneratorInterface $class)
26 | {
27 | $class->addMethodFromGenerator($this->generateToStreamMethod($entity));
28 | }
29 |
30 | /**
31 | * @param \Protobuf\Compiler\Entity $entity
32 | *
33 | * @return \Zend\Code\Generator\GeneratorInterface
34 | */
35 | protected function generateToStreamMethod(Entity $entity)
36 | {
37 | $lines = $this->generateBody($entity);
38 | $body = implode(PHP_EOL, $lines);
39 | $method = MethodGenerator::fromArray([
40 | 'name' => 'toStream',
41 | 'body' => $body,
42 | 'parameters' => [
43 | [
44 | 'name' => 'configuration',
45 | 'type' => '\Protobuf\Configuration',
46 | 'defaultValue' => null
47 | ]
48 | ],
49 | 'docblock' => [
50 | 'shortDescription' => "{@inheritdoc}"
51 | ]
52 | ]);
53 |
54 | return $method;
55 | }
56 |
57 | /**
58 | * @return string[]
59 | */
60 | public function generateBody()
61 | {
62 | $body[] = '$config = $configuration ?: \Protobuf\Configuration::getInstance();';
63 | $body[] = '$context = $config->createWriteContext();';
64 | $body[] = '$stream = $context->getStream();';
65 | $body[] = null;
66 | $body[] = '$this->writeTo($context);';
67 | $body[] = '$stream->seek(0);';
68 | $body[] = null;
69 | $body[] = 'return $stream;';
70 |
71 | return $body;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/tests/Fixtures/Person/PhoneType.tpl:
--------------------------------------------------------------------------------
1 |
18 | */
19 | class Generator extends BaseGenerator
20 | {
21 | /**
22 | * @var array
23 | */
24 | protected $generators;
25 | /**
26 | * @param \Protobuf\Compiler\Context $context
27 | */
28 | public function __construct(Context $context)
29 | {
30 | $this->context = $context;
31 | $this->generators = [
32 | Entity::TYPE_ENUM => new EnumGenerator($context),
33 | Entity::TYPE_SERVICE => new ServiceGenerator($context),
34 | Entity::TYPE_MESSAGE => new MessageGenerator($context),
35 | Entity::TYPE_EXTENSION => new ExtensionGenerator($context)
36 | ];
37 | }
38 |
39 | /**
40 | * @param Entity $entity
41 | */
42 | public function visit(Entity $entity)
43 | {
44 | $type = $entity->getType();
45 | $class = $entity->getNamespacedName();
46 | $path = $this->getPsr4ClassPath($class);
47 |
48 | $entity->setPath($path);
49 |
50 | if ( ! isset($this->generators[$type])) {
51 | return;
52 | }
53 |
54 | $this->generators[$type]->visit($entity);
55 | }
56 |
57 | /**
58 | * @param string $class
59 | *
60 | * @return string
61 | */
62 | protected function getPsr4ClassPath($class)
63 | {
64 | $options = $this->context->getOptions();
65 | $psr4 = $options->getPsr4() ?: [];
66 | $class = trim($class, '\\');
67 |
68 | foreach ($psr4 as $item) {
69 | $prefix = trim($item, '\\') . '\\';
70 | $length = strlen($prefix);
71 | $start = substr($class, 0, $length);
72 |
73 | if ($start !== $prefix) {
74 | continue;
75 | }
76 |
77 | $name = trim(str_replace($prefix, '', $class), '\\');
78 | $path = $this->getClassPath($name);
79 |
80 | return $path;
81 | }
82 |
83 | return $this->getClassPath($class);
84 | }
85 |
86 | /**
87 | * @param string $class
88 | *
89 | * @return string
90 | */
91 | protected function getClassPath($class)
92 | {
93 | return str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php';
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/tests/Descriptor/MessageDescriptorTest.php:
--------------------------------------------------------------------------------
1 | markTestIncompleteIfProtoClassNotFound();
25 |
26 | parent::setUp();
27 | }
28 |
29 | public function testComplexMessageDescriptor()
30 | {
31 | $descriptor = AddressBook::descriptor();
32 |
33 | $this->assertInstanceOf(DescriptorProto::CLASS, $descriptor);
34 | $this->assertEquals('AddressBook', $descriptor->getName());
35 |
36 | $this->assertCount(1, $descriptor->getFieldList());
37 |
38 | $this->assertInstanceOf(FieldDescriptorProto::CLASS, $descriptor->getFieldList()[0]);
39 | $this->assertEquals(1, $descriptor->getFieldList()[0]->getNumber());
40 | $this->assertEquals('person', $descriptor->getFieldList()[0]->getName());
41 | $this->assertSame(Type::TYPE_MESSAGE(), $descriptor->getFieldList()[0]->getType());
42 | $this->assertSame(Label::LABEL_REPEATED(), $descriptor->getFieldList()[0]->getLabel());
43 | $this->assertEquals('.ProtobufCompilerTest.Protos.Person', $descriptor->getFieldList()[0]->getTypeName());
44 | }
45 |
46 | public function testMessageDescriptorOptionsExtensions()
47 | {
48 | $descriptor = Options\MyMessage::descriptor();
49 |
50 | $this->assertInstanceOf(DescriptorProto::CLASS, $descriptor);
51 | $this->assertEquals('MyMessage', $descriptor->getName());
52 |
53 | $this->assertInstanceOf(MessageOptions::CLASS, $descriptor->getOptions());
54 |
55 | $extensions = $descriptor->getOptions()->extensions();
56 |
57 | $this->assertCount(2, $extensions);
58 |
59 | $this->assertContains(Options\Extension::myMessageOption(), $extensions);
60 | $this->assertContains(Options\Extension::myMessageOptionMsg(), $extensions);
61 |
62 | $myMessageOption = $extensions->get(Options\Extension::myMessageOption());
63 | $myMessageOptionMsg = $extensions->get(Options\Extension::myMessageOptionMsg());
64 |
65 | $this->assertSame(1234, $myMessageOption);
66 | $this->assertInstanceOf(Options\MyOption::CLASS, $myMessageOptionMsg);
67 | $this->assertSame('one,two,three,four', $myMessageOptionMsg->getLabel());
68 | $this->assertSame('1234', $myMessageOptionMsg->getValue());
69 | $this->assertSame(0, $myMessageOptionMsg->getVal());
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Generator/MessageGenerator.php:
--------------------------------------------------------------------------------
1 |
27 | */
28 | class MessageGenerator extends BaseGenerator implements EntityVisitor
29 | {
30 | /**
31 | * @var array
32 | */
33 | protected $generators;
34 |
35 | /**
36 | * @param \Protobuf\Compiler\Context $context
37 | */
38 | public function __construct(Context $context)
39 | {
40 | parent::__construct($context);
41 |
42 | $this->generators = [
43 | new ConstructGenerator($context),
44 | new FieldsGenerator($context),
45 | new ExtensionsGenerator($context),
46 | new FromStreamGenerator($context),
47 | new FromArrayGenerator($context),
48 | new DescriptorGenerator($context),
49 | new ToStreamGenerator($context),
50 | new WriteToGenerator($context),
51 | new ReadFromGenerator($context),
52 | new SerializedSizeGenerator($context),
53 | new ClearGenerator($context),
54 | new MergeGenerator($context),
55 | ];
56 | }
57 |
58 | /**
59 | * {@inheritdoc}
60 | */
61 | public function visit(Entity $entity)
62 | {
63 | $name = $entity->getName();
64 | $namespace = $entity->getNamespace();
65 | $descriptor = $entity->getDescriptor();
66 | $shortDescription = 'Protobuf message : ' . $entity->getClass();
67 | $class = ClassGenerator::fromArray([
68 | 'name' => $name,
69 | 'namespacename' => $namespace,
70 | 'extendedClass' => '\Protobuf\AbstractMessage',
71 | 'docblock' => [
72 | 'shortDescription' => $shortDescription
73 | ]
74 | ]);
75 |
76 | foreach ($this->generators as $generator) {
77 | $generator->visit($entity, $class);
78 | }
79 |
80 | $entity->setContent($this->generateFileContent($class, $entity));
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/tests/Resources/proto3/route_guide.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package ProtobufCompilerTest.Protos.RouteGuide;
4 |
5 | // Interface exported by the server.
6 | service RouteGuide {
7 | // A simple RPC.
8 | //
9 | // Obtains the feature at a given position.
10 | rpc GetFeature(Point) returns (Feature) {}
11 |
12 | // A server-to-client streaming RPC.
13 | //
14 | // Obtains the Features available within the given Rectangle. Results are
15 | // streamed rather than returned at once (e.g. in a response message with a
16 | // repeated field), as the rectangle may cover a large area and contain a
17 | // huge number of features.
18 | rpc ListFeatures(Rectangle) returns (stream Feature) {}
19 |
20 | // A client-to-server streaming RPC.
21 | //
22 | // Accepts a stream of Points on a route being traversed, returning a
23 | // RouteSummary when traversal is completed.
24 | rpc RecordRoute(stream Point) returns (RouteSummary) {}
25 |
26 | // A Bidirectional streaming RPC.
27 | //
28 | // Accepts a stream of RouteNotes sent while a route is being traversed,
29 | // while receiving other RouteNotes (e.g. from other users).
30 | rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
31 | }
32 |
33 | // Points are represented as latitude-longitude pairs in the E7 representation
34 | // (degrees multiplied by 10**7 and rounded to the nearest integer).
35 | // Latitudes should be in the range +/- 90 degrees and longitude should be in
36 | // the range +/- 180 degrees (inclusive).
37 | message Point {
38 | int32 latitude = 1;
39 | int32 longitude = 2;
40 | }
41 |
42 | // A latitude-longitude rectangle, represented as two diagonally opposite
43 | // points "lo" and "hi".
44 | message Rectangle {
45 | // One corner of the rectangle.
46 | Point lo = 1;
47 |
48 | // The other corner of the rectangle.
49 | Point hi = 2;
50 | }
51 |
52 | // A feature names something at a given point.
53 | //
54 | // If a feature could not be named, the name is empty.
55 | message Feature {
56 | // The name of the feature.
57 | string name = 1;
58 |
59 | // The point where the feature is detected.
60 | Point location = 2;
61 | }
62 |
63 | // A RouteNote is a message sent while at a given point.
64 | message RouteNote {
65 | // The location from which the message is sent.
66 | Point location = 1;
67 |
68 | // The message to be sent.
69 | string message = 2;
70 | }
71 |
72 | // A RouteSummary is received in response to a RecordRoute rpc.
73 | //
74 | // It contains the number of individual points received, the number of
75 | // detected features, and the total distance covered as the cumulative sum of
76 | // the distance between each point.
77 | message RouteSummary {
78 | // The number of points received.
79 | int32 point_count = 1;
80 |
81 | // The number of known features passed while traversing the route.
82 | int32 feature_count = 2;
83 |
84 | // The distance covered in metres.
85 | int32 distance = 3;
86 |
87 | // The duration of the traversal in seconds.
88 | int32 elapsed_time = 4;
89 | }
--------------------------------------------------------------------------------
/tests/Generator/Message/ReadFromGeneratorTest.php:
--------------------------------------------------------------------------------
1 | createContext([
17 | [
18 | 'name' => 'simple.proto',
19 | 'package' => 'ProtobufCompilerTest.Protos',
20 | 'values' => [
21 | 'messages' => [
22 | [
23 | 'name' => 'Simple',
24 | 'fields' => [
25 | 1 => ['lines', Field::TYPE_INT32, Field::LABEL_REPEATED]
26 | ]
27 | ]
28 | ]
29 | ]
30 | ]
31 | ]);
32 |
33 | $entity = $context->getEntity('ProtobufCompilerTest.Protos.Simple');
34 | $generator = new ReadFromGenerator($context);
35 | $descritor = $entity->getDescriptor();
36 | $field = $descritor->getFieldList()[0];
37 |
38 | $actual = $this->invokeMethod($generator, 'generateBody', [$entity]);
39 | $expected = <<<'CODE'
40 | $reader = $context->getReader();
41 | $length = $context->getLength();
42 | $stream = $context->getStream();
43 |
44 | $limit = ($length !== null)
45 | ? ($stream->tell() + $length)
46 | : null;
47 |
48 | while ($limit === null || $stream->tell() < $limit) {
49 |
50 | if ($stream->eof()) {
51 | break;
52 | }
53 |
54 | $key = $reader->readVarint($stream);
55 | $wire = \Protobuf\WireFormat::getTagWireType($key);
56 | $tag = \Protobuf\WireFormat::getTagFieldNumber($key);
57 |
58 | if ($stream->eof()) {
59 | break;
60 | }
61 |
62 | if ($tag === 1) {
63 | \Protobuf\WireFormat::assertWireType($wire, 5);
64 |
65 | if ($this->lines === null) {
66 | $this->lines = new \Protobuf\ScalarCollection();
67 | }
68 |
69 | $this->lines->add($reader->readVarint($stream));
70 |
71 | continue;
72 | }
73 |
74 | $extensions = $context->getExtensionRegistry();
75 | $extension = $extensions ? $extensions->findByNumber(__CLASS__, $tag) : null;
76 |
77 | if ($extension !== null) {
78 | $this->extensions()->add($extension, $extension->readFrom($context, $wire));
79 |
80 | continue;
81 | }
82 |
83 | if ($this->unknownFieldSet === null) {
84 | $this->unknownFieldSet = new \Protobuf\UnknownFieldSet();
85 | }
86 |
87 | $data = $reader->readUnknown($stream, $wire);
88 | $unknown = new \Protobuf\Unknown($tag, $wire, $data);
89 |
90 | $this->unknownFieldSet->add($unknown);
91 |
92 | }
93 | CODE;
94 |
95 | $this->assertEquals($expected, implode(PHP_EOL, $actual));
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/Command/Application.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class Application extends SymfonyApplication
16 | {
17 | /**
18 | * @var \Symfony\Component\Console\Command\Command
19 | */
20 | private $generateCommand;
21 |
22 | /**
23 | * @var \Symfony\Component\Console\Command\Command
24 | */
25 | private $pluginCommand;
26 |
27 | /**
28 | * Constructor.
29 | *
30 | * @param \Symfony\Component\Console\Command\Command $generateCommand
31 | * @param \Symfony\Component\Console\Command\Command $pluginCommand
32 | */
33 | public function __construct(Command $generateCommand, Command $pluginCommand)
34 | {
35 | $this->generateCommand = $generateCommand;
36 | $this->pluginCommand = $pluginCommand;
37 |
38 | parent::__construct('protobuf');
39 | }
40 |
41 | /**
42 | * {@inheritdoc}
43 | */
44 | protected function getCommandName(InputInterface $input)
45 | {
46 | $stream = $this->getStdinStream();
47 | $hasStdin = $stream->getSize() > 0;
48 |
49 | $this->pluginCommand->setStream($stream);
50 |
51 | return $hasStdin
52 | ? $this->pluginCommand->getName()
53 | : $this->generateCommand->getName();
54 | }
55 |
56 | /**
57 | * {@inheritdoc}
58 | */
59 | protected function getDefaultCommands()
60 | {
61 | $commands = parent::getDefaultCommands();
62 | $commands[] = $this->generateCommand;
63 | $commands[] = $this->pluginCommand;
64 |
65 | return $commands;
66 | }
67 |
68 | /**
69 | * {@inheritdoc}
70 | */
71 | public function getDefinition()
72 | {
73 | $definition = parent::getDefinition();
74 |
75 | $definition->setArguments();
76 |
77 | return $definition;
78 | }
79 |
80 | /**
81 | * @return \Protobuf\Stream
82 | */
83 | protected function getStdinStream()
84 | {
85 | $handle = fopen('php://stdin', 'r');
86 | $stream = Stream::create();
87 | $counter = 0;
88 |
89 | stream_set_blocking($handle, false);
90 |
91 | while ( ! feof($handle) && ($counter++ < 10)) {
92 |
93 | $buffer = fread($handle, 1024);
94 | $length = mb_strlen($buffer, '8bit');
95 |
96 | if ($length > 0) {
97 |
98 | $stream->write($buffer, $length);
99 | $counter = 0;
100 |
101 | continue;
102 | }
103 |
104 | usleep(1000);
105 | }
106 |
107 | $stream->seek(0);
108 | fclose($handle);
109 |
110 | return $stream;
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/tests/Command/PluginCommandTest.php:
--------------------------------------------------------------------------------
1 | getMockBuilder('Protobuf\Compiler\Compiler')
16 | ->disableOriginalConstructor()
17 | ->getMock();
18 |
19 | $streamIn = $this->getMockBuilder('Protobuf\Stream')
20 | ->disableOriginalConstructor()
21 | ->getMock();
22 |
23 | $streamOut = $this->getMockBuilder('Protobuf\Stream')
24 | ->disableOriginalConstructor()
25 | ->getMock();
26 | $mock = $this->getMockBuilder(PluginCommand::CLASS)
27 | ->setMethods(['createCompiler', 'writeStream'])
28 | ->getMock();
29 |
30 | $mock->setStream($streamIn);
31 |
32 | $mock->expects($this->once())
33 | ->method('createCompiler')
34 | ->willReturn($compiler);
35 |
36 | $mock->expects($this->once())
37 | ->method('writeStream')
38 | ->with($streamOut);
39 |
40 | $compiler->expects($this->once())
41 | ->method('compile')
42 | ->willReturn($streamOut)
43 | ->with($this->equalTo($streamIn));
44 |
45 | $application = new Application();
46 |
47 | $application->add($mock);
48 |
49 | $command = $application->find('protobuf:plugin');
50 | $commandTester = new CommandTester($command);
51 |
52 | $commandTester->execute([]);
53 | }
54 |
55 | /**
56 | * @expectedException RuntimeException
57 | * @expectedExceptionMessage Unable to read standard input
58 | */
59 | public function testExecuteException()
60 | {
61 | $application = new Application();
62 | $mock = $this->getMockBuilder(PluginCommand::CLASS)
63 | ->setMethods(['createCompiler', 'createStream'])
64 | ->getMock();
65 |
66 | $application->add($mock);
67 |
68 | $command = $application->find('protobuf:plugin');
69 | $commandTester = new CommandTester($command);
70 |
71 | $commandTester->execute([]);
72 | }
73 |
74 | public function testCreateProcessBuilder()
75 | {
76 | $command = new PluginCommand();
77 | $output = $this->getMock('Symfony\Component\Console\Output\OutputInterface');
78 | $compiler = $this->invokeMethod($command, 'createCompiler', [$output]);
79 |
80 | $this->assertInstanceOf('Protobuf\Compiler\Compiler', $compiler);
81 | }
82 |
83 | public function testCreateConsoleLogger()
84 | {
85 | $command = new PluginCommand();
86 | $output = $this->getMock('Symfony\Component\Console\Output\OutputInterface');
87 | $compiler = $this->invokeMethod($command, 'createConsoleLogger', [$output]);
88 |
89 | $this->assertInstanceOf('Symfony\Component\Console\Logger\ConsoleLogger', $compiler);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/Generator/Message/MergeGenerator.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | class MergeGenerator extends BaseGenerator implements GeneratorVisitor
22 | {
23 | /**
24 | * {@inheritdoc}
25 | */
26 | public function visit(Entity $entity, GeneratorInterface $class)
27 | {
28 | $class->addMethodFromGenerator($this->generateMethod($entity));
29 | }
30 |
31 | /**
32 | * @param \Protobuf\Compiler\Entity $entity
33 | *
34 | * @return MethodGenerator
35 | */
36 | protected function generateMethod(Entity $entity)
37 | {
38 | $lines = $this->generateBody($entity);
39 | $body = implode(PHP_EOL, $lines);
40 | $method = MethodGenerator::fromArray([
41 | 'name' => 'merge',
42 | 'body' => $body,
43 | 'parameters' => [
44 | [
45 | 'name' => 'message',
46 | 'type' => '\Protobuf\Message'
47 | ]
48 | ],
49 | 'docblock' => [
50 | 'shortDescription' => "{@inheritdoc}"
51 | ]
52 | ]);
53 |
54 | return $method;
55 | }
56 |
57 | /**
58 | * @param \Protobuf\Compiler\Entity $entity
59 | *
60 | * @return string[]
61 | */
62 | public function generateBody(Entity $entity)
63 | {
64 | $lines = [];
65 | $descriptor = $entity->getDescriptor();
66 | $class = $entity->getNamespacedName();
67 | $fields = $descriptor->getFieldList() ?: [];
68 | $message = var_export("Argument 1 passed to %s must be a %s, %s given", true);
69 | $exception = 'sprintf(' . $message . ', __METHOD__, __CLASS__, get_class($message))';
70 |
71 | $lines[] = 'if ( ! $message instanceof ' . $class . ') {';
72 | $lines[] = ' throw new \InvalidArgumentException(' . $exception . ');';
73 | $lines[] = '}';
74 | $lines[] = null;
75 |
76 | foreach ($fields as $field) {
77 | $item = $this->generateFieldMerge($entity, $field);
78 | $lines = array_merge($lines, $item);
79 | }
80 |
81 | return $lines;
82 | }
83 |
84 | /**
85 | * @param \Protobuf\Compiler\Entity $entity
86 | * @param \google\protobuf\FieldDescriptorProto $field
87 | *
88 | * @return array
89 | */
90 | protected function generateFieldMerge(Entity $entity, FieldDescriptorProto $field)
91 | {
92 | $lines = [];
93 | $fieldName = $field->getName();
94 | $format = '$this->%s = ($message->%s !== null) ? $message->%s : $this->%s;';
95 | $lines[] = sprintf($format, $fieldName, $fieldName, $fieldName, $fieldName);
96 |
97 | return $lines;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/Generator/Message/ConstructGenerator.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | class ConstructGenerator extends BaseGenerator implements GeneratorVisitor
18 | {
19 | /**
20 | * {@inheritdoc}
21 | */
22 | public function visit(Entity $entity, GeneratorInterface $class)
23 | {
24 | if ( ! $this->hasDefaultValue($entity)) {
25 | return;
26 | }
27 |
28 | $class->addMethodFromGenerator($this->generateConstructorMethod($entity));
29 | }
30 |
31 | /**
32 | * @param \Protobuf\Compiler\Entity $entity
33 | *
34 | * @return \Zend\Code\Generator\GeneratorInterface
35 | */
36 | protected function generateConstructorMethod(Entity $entity)
37 | {
38 | $lines = $this->generateBody($entity);
39 | $body = implode(PHP_EOL, $lines);
40 | $method = MethodGenerator::fromArray([
41 | 'name' => '__construct',
42 | 'body' => $body,
43 | 'parameters' => [
44 | [
45 | 'name' => 'stream',
46 | 'type' => 'mixed',
47 | 'defaultValue' => null
48 | ],
49 | [
50 | 'name' => 'configuration',
51 | 'type' => '\Protobuf\Configuration',
52 | 'defaultValue' => null
53 | ]
54 | ],
55 | 'docblock' => [
56 | 'shortDescription' => '{@inheritdoc}'
57 | ]
58 | ]);
59 |
60 | return $method;
61 | }
62 |
63 | /**
64 | * @param \Protobuf\Compiler\Entity $entity
65 | *
66 | * @return string[]
67 | */
68 | public function generateBody(Entity $entity)
69 | {
70 | $body = [];
71 | $descriptor = $entity->getDescriptor();
72 | $fields = $descriptor->getFieldList() ?: [];
73 |
74 | foreach ($fields as $field) {
75 | if ( ! $field->hasDefaultValue()) {
76 | continue;
77 | }
78 |
79 | $name = $field->getName();
80 | $value = $this->getDefaultFieldValue($field);
81 |
82 | $body[] = sprintf('$this->%s = %s;', $name, $value);
83 | }
84 |
85 | $body[] = null;
86 | $body[] = 'parent::__construct($stream, $configuration);';
87 |
88 | return $body;
89 | }
90 |
91 | /**
92 | * @param \Protobuf\Compiler\Entity $entity
93 | *
94 | * @return bool
95 | */
96 | public function hasDefaultValue(Entity $entity)
97 | {
98 | $descriptor = $entity->getDescriptor();
99 | $fields = $descriptor->getFieldList() ?: [];
100 |
101 | foreach ($fields as $field) {
102 | if ( ! $field->hasDefaultValue()) {
103 | continue;
104 | }
105 |
106 | return true;
107 | }
108 |
109 | return false;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/Command/PluginCommand.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | class PluginCommand extends Command
23 | {
24 |
25 | /**
26 | * @var \Protobuf\Stream
27 | */
28 | private $stream;
29 |
30 | /**
31 | * @param \Protobuf\Stream $stream
32 | */
33 | public function setStream(Stream $stream)
34 | {
35 | $this->stream = $stream;
36 | }
37 |
38 | /**
39 | * {@inheritdoc}
40 | */
41 | protected function configure()
42 | {
43 | $this
44 | ->setName('protobuf:plugin')
45 | ->setDescription('Plugin to generate PHP classes from stdin generated by protoc');
46 | }
47 |
48 | /**
49 | * @param \Protobuf\Stream $stream
50 | */
51 | protected function writeStream(Stream $stream)
52 | {
53 | // OutputInterface#write messes with the content
54 | fwrite(STDOUT, $stream);
55 | }
56 |
57 | /**
58 | * {@inheritdoc}
59 | */
60 | protected function execute(InputInterface $input, OutputInterface $output)
61 | {
62 | if ($this->stream === null) {
63 | throw new RuntimeException("Unable to read standard input.");
64 | }
65 |
66 | $compiler = $this->createCompiler($output);
67 | $response = $compiler->compile($this->stream);
68 |
69 | $this->writeStream($response);
70 | }
71 |
72 | /**
73 | * @param \Symfony\Component\Console\Output\OutputInterface $output
74 | *
75 | * @return \Protobuf\Compiler\Compiler
76 | */
77 | protected function createCompiler(OutputInterface $output)
78 | {
79 | $logger = $this->createConsoleLogger($output);
80 | $compiler = new Compiler($logger);
81 |
82 | return $compiler;
83 | }
84 |
85 | /**
86 | * @param \Symfony\Component\Console\Output\OutputInterface $output
87 | *
88 | * @return \Symfony\Component\Console\Logger\ConsoleLogger
89 | */
90 | protected function createConsoleLogger(OutputInterface $output)
91 | {
92 | return new ConsoleLogger(
93 | $output,
94 | [
95 | // aways output notice, info and debug
96 | LogLevel::NOTICE => OutputInterface::VERBOSITY_NORMAL,
97 | LogLevel::INFO => OutputInterface::VERBOSITY_NORMAL,
98 | LogLevel::DEBUG => OutputInterface::VERBOSITY_NORMAL
99 | ],
100 | [
101 | // redirect messages to stderr
102 | LogLevel::WARNING => ConsoleLogger::INFO,
103 | LogLevel::NOTICE => ConsoleLogger::ERROR,
104 | LogLevel::INFO => ConsoleLogger::ERROR,
105 | LogLevel::DEBUG => ConsoleLogger::ERROR
106 | ]
107 | );
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/tests/CompilerTest.php:
--------------------------------------------------------------------------------
1 | logger = $this->getMock('Psr\Log\LoggerInterface');
23 | }
24 |
25 | public function testGenerateSimpleMessage()
26 | {
27 | $binaryRequest = $this->getFixtureFileContent('compiler/generator-request-simple.bin');
28 | $expectedContent = $this->getFixtureFileContent('Simple.tpl');
29 |
30 | $compiler = new Compiler($this->logger);
31 | $binaryResponse = $compiler->compile(Stream::wrap($binaryRequest));
32 | $response = CodeGeneratorResponse::fromStream($binaryResponse);
33 |
34 | $this->assertInstanceOf('google\protobuf\compiler\CodeGeneratorResponse', $response);
35 | $this->assertInstanceOf('Protobuf\Collection', $response->getFileList());
36 | $this->assertCount(1, $response->getFileList());
37 |
38 | $file = $response->getFileList()[0];
39 |
40 | $this->assertInstanceOf('google\protobuf\compiler\CodeGeneratorResponse\File', $file);
41 | $this->assertEquals('ProtobufCompilerTest/Protos/Simple.php', $file->getName());
42 | $this->assertEquals($expectedContent, $file->getContent());
43 | }
44 |
45 | public function testLoadEntityClassIgnoreExistinClass()
46 | {
47 | $compiler = new Compiler($this->logger);
48 | $entity = $this->getMock(Entity::CLASS, [], [], '', false);
49 |
50 | $entity->expects($this->once())
51 | ->method('getType')
52 | ->willReturn(Entity::TYPE_MESSAGE);
53 |
54 | $entity->expects($this->once())
55 | ->method('getContent')
56 | ->willReturn('expects($this->once())
59 | ->method('getNamespacedName')
60 | ->willReturn('\\Iterator');
61 |
62 | $this->assertFalse($this->invokeMethod($compiler, 'loadEntityClass', [$entity]));
63 | }
64 |
65 | public function testLoadEntityExtensionClass()
66 | {
67 | $unique = uniqid();
68 | $compiler = new Compiler($this->logger);
69 | $entity = $this->getMock(Entity::CLASS, [], [], '', false);
70 | $class = "ProtobufCompilerTest\CompilerTest$unique\Extension";
71 | $code = <<expects($this->once())
82 | ->method('getType')
83 | ->willReturn(Entity::TYPE_EXTENSION);
84 |
85 | $entity->expects($this->once())
86 | ->method('getContent')
87 | ->willReturn($code);
88 |
89 | $entity->expects($this->once())
90 | ->method('getNamespacedName')
91 | ->willReturn($class);
92 |
93 | $this->assertFalse(class_exists($class));
94 | $this->assertTrue($this->invokeMethod($compiler, 'loadEntityClass', [$entity]));
95 | $this->assertTrue(class_exists($class));
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/tests/Descriptor/MergeTest.php:
--------------------------------------------------------------------------------
1 | markTestIncompleteIfProtoClassNotFound();
22 |
23 | parent::setUp();
24 | }
25 |
26 | public function testSimpleMessageMerge()
27 | {
28 | $simple1 = new Simple();
29 | $simple2 = new Simple();
30 | $bytes = Stream::wrap("bar");
31 |
32 | $simple1->setBool(true);
33 | $simple1->setString("foo");
34 | $simple1->setBytes($bytes);
35 | $simple1->setFloat(12345.123);
36 | $simple1->setUint32(123456789);
37 | $simple1->setInt32(-123456789);
38 | $simple1->setFixed32(123456789);
39 | $simple1->setSint32(-123456789);
40 | $simple1->setSfixed32(-123456789);
41 | $simple1->setDouble(123456789.12345);
42 | $simple1->setInt64(-123456789123456789);
43 | $simple1->setUint64(123456789123456789);
44 | $simple1->setFixed64(123456789123456789);
45 | $simple1->setSint64(-123456789123456789);
46 | $simple1->setSfixed64(-123456789123456789);
47 |
48 | $simple2->merge($simple1);
49 |
50 | $this->assertSame(true, $simple2->getBool());
51 | $this->assertSame("foo", $simple2->getString());
52 | $this->assertSame($bytes, $simple2->getBytes());
53 | $this->assertSame(12345.123, $simple2->getFloat());
54 | $this->assertSame(123456789, $simple2->getUint32());
55 | $this->assertSame(-123456789, $simple2->getInt32());
56 | $this->assertSame(123456789, $simple2->getFixed32());
57 | $this->assertSame(-123456789, $simple2->getSint32());
58 | $this->assertSame(-123456789, $simple2->getSfixed32());
59 | $this->assertSame(123456789.12345, $simple2->getDouble());
60 | $this->assertSame(-123456789123456789, $simple2->getInt64());
61 | $this->assertSame(123456789123456789, $simple2->getUint64());
62 | $this->assertSame(123456789123456789, $simple2->getFixed64());
63 | $this->assertSame(-123456789123456789, $simple2->getSint64());
64 | $this->assertSame(-123456789123456789, $simple2->getSfixed64());
65 | }
66 |
67 | public function testSimpleMessageMergeNullComparison()
68 | {
69 | $simple1 = new Simple();
70 | $simple2 = new Simple();
71 | $bytes = Stream::wrap("bar");
72 |
73 | $simple1->setBool(false);
74 | $simple1->setFloat(0.0);
75 | $simple1->setUint32(0);
76 |
77 | $simple2->merge($simple1);
78 |
79 | $this->assertSame(false, $simple2->getBool());
80 | $this->assertSame(0.0, $simple2->getFloat());
81 | $this->assertSame(0, $simple2->getUint32());
82 | }
83 |
84 | /**
85 | * @expectedException \InvalidArgumentException
86 | * @expectedExceptionMessage Argument 1 passed to ProtobufCompilerTest\Protos\Simple::merge must be a ProtobufCompilerTest\Protos\Simple, ProtobufCompilerTest\Protos\Person given
87 | */
88 | public function testMergeException()
89 | {
90 | $simple = new Simple();
91 | $person = new Person();
92 |
93 | $simple->merge($person);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/tests/Descriptor/ClearTest.php:
--------------------------------------------------------------------------------
1 | markTestIncompleteIfProtoClassNotFound();
23 |
24 | parent::setUp();
25 | }
26 |
27 | public function testSimpleMessageClear()
28 | {
29 | $simple = new Simple();
30 |
31 | $simple->setBool(true);
32 | $simple->setString("foo");
33 | $simple->setFloat(12345.123);
34 | $simple->setUint32(123456789);
35 | $simple->setInt32(-123456789);
36 | $simple->setFixed32(123456789);
37 | $simple->setSint32(-123456789);
38 | $simple->setSfixed32(-123456789);
39 | $simple->setDouble(123456789.12345);
40 | $simple->setInt64(-123456789123456789);
41 | $simple->setUint64(123456789123456789);
42 | $simple->setFixed64(123456789123456789);
43 | $simple->setSint64(-123456789123456789);
44 | $simple->setBytes(Stream::wrap("bar"));
45 | $simple->setSfixed64(-123456789123456789);
46 |
47 | $this->assertSame(true, $simple->getBool());
48 | $this->assertSame("foo", $simple->getString());
49 | $this->assertSame(12345.123, $simple->getFloat());
50 | $this->assertSame(123456789, $simple->getUint32());
51 | $this->assertSame(-123456789, $simple->getInt32());
52 | $this->assertSame(123456789, $simple->getFixed32());
53 | $this->assertSame(-123456789, $simple->getSint32());
54 | $this->assertSame(-123456789, $simple->getSfixed32());
55 | $this->assertSame(123456789.12345, $simple->getDouble());
56 | $this->assertSame(-123456789123456789, $simple->getInt64());
57 | $this->assertSame(123456789123456789, $simple->getUint64());
58 | $this->assertSame(123456789123456789, $simple->getFixed64());
59 | $this->assertSame(-123456789123456789, $simple->getSint64());
60 | $this->assertSame(-123456789123456789, $simple->getSfixed64());
61 | $this->assertInstanceOf('Protobuf\Stream', $simple->getBytes());
62 |
63 | $simple->clear();
64 |
65 | $this->assertNull($simple->getBool());
66 | $this->assertNull($simple->getString());
67 | $this->assertNull($simple->getFloat());
68 | $this->assertNull($simple->getUint32());
69 | $this->assertNull($simple->getInt32());
70 | $this->assertNull($simple->getFixed32());
71 | $this->assertNull($simple->getSint32());
72 | $this->assertNull($simple->getSfixed32());
73 | $this->assertNull($simple->getDouble());
74 | $this->assertNull($simple->getInt64());
75 | $this->assertNull($simple->getUint64());
76 | $this->assertNull($simple->getFixed64());
77 | $this->assertNull($simple->getSint64());
78 | $this->assertNull($simple->getSfixed64());
79 | $this->assertNull($simple->getBytes());
80 | }
81 |
82 | public function testClearMessageWithDefaultValue()
83 | {
84 | $phone = new PhoneNumber();
85 |
86 | $this->assertNull($phone->getNumber());
87 | $this->assertSame(PhoneType::HOME(), $phone->getType());
88 |
89 | $phone->setNumber('1231231212');
90 | $phone->setType(PhoneType::MOBILE());
91 |
92 | $this->assertEquals('1231231212', $phone->getNumber());
93 | $this->assertSame(PhoneType::MOBILE(), $phone->getType());
94 |
95 | $phone->clear();
96 |
97 | $this->assertNull($phone->getNumber());
98 | $this->assertSame(PhoneType::HOME(), $phone->getType());
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/tests/Fixtures/Extension/Extension.tpl:
--------------------------------------------------------------------------------
1 | add(\ProtobufCompilerTest\Protos\Extension\Dog::animal());
39 | $registry->add(self::habitat());
40 | $registry->add(self::verbose());
41 | }
42 |
43 | /**
44 | * Extension field : habitat
45 | *
46 | * @return \Protobuf\Extension
47 | */
48 | public static function habitat()
49 | {
50 | if (self::$habitat !== null) {
51 | return self::$habitat;
52 | }
53 |
54 | $readCallback = function (\Protobuf\ReadContext $context, $wire) {
55 | $reader = $context->getReader();
56 | $length = $context->getLength();
57 | $stream = $context->getStream();
58 |
59 | \Protobuf\WireFormat::assertWireType($wire, 9);
60 |
61 | $value = $reader->readString($stream);
62 |
63 | return $value;
64 | };
65 |
66 | $writeCallback = function (\Protobuf\WriteContext $context, $value) {
67 | $stream = $context->getStream();
68 | $writer = $context->getWriter();
69 | $sizeContext = $context->getComputeSizeContext();
70 |
71 | $writer->writeVarint($stream, 1602);
72 | $writer->writeString($stream, $value);
73 | };
74 |
75 | $sizeCallback = function (\Protobuf\ComputeSizeContext $context, $value) {
76 | $calculator = $context->getSizeCalculator();
77 | $size = 0;
78 |
79 | $size += 2;
80 | $size += $calculator->computeStringSize($value);
81 |
82 | return $size;
83 | };
84 |
85 | return self::$habitat = new \Protobuf\Extension\ExtensionField('\\ProtobufCompilerTest\\Protos\\Extension\\Animal', 'habitat', 200, $readCallback, $writeCallback, $sizeCallback, __METHOD__);
86 | }
87 |
88 | /**
89 | * Extension field : verbose
90 | *
91 | * @return \Protobuf\Extension
92 | */
93 | public static function verbose()
94 | {
95 | if (self::$verbose !== null) {
96 | return self::$verbose;
97 | }
98 |
99 | $readCallback = function (\Protobuf\ReadContext $context, $wire) {
100 | $reader = $context->getReader();
101 | $length = $context->getLength();
102 | $stream = $context->getStream();
103 |
104 | \Protobuf\WireFormat::assertWireType($wire, 8);
105 |
106 | $value = $reader->readBool($stream);
107 |
108 | return $value;
109 | };
110 |
111 | $writeCallback = function (\Protobuf\WriteContext $context, $value) {
112 | $stream = $context->getStream();
113 | $writer = $context->getWriter();
114 | $sizeContext = $context->getComputeSizeContext();
115 |
116 | $writer->writeVarint($stream, 1608);
117 | $writer->writeBool($stream, $value);
118 | };
119 |
120 | $sizeCallback = function (\Protobuf\ComputeSizeContext $context, $value) {
121 | $calculator = $context->getSizeCalculator();
122 | $size = 0;
123 |
124 | $size += 2;
125 | $size += 1;
126 |
127 | return $size;
128 | };
129 |
130 | return self::$verbose = new \Protobuf\Extension\ExtensionField('\\ProtobufCompilerTest\\Protos\\Extension\\Command', 'verbose', 201, $readCallback, $writeCallback, $sizeCallback, __METHOD__);
131 | }
132 |
133 |
134 | }
135 |
136 |
--------------------------------------------------------------------------------
/src/Generator/Message/SerializedSizeGenerator.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | class SerializedSizeGenerator extends BaseGenerator implements GeneratorVisitor
21 | {
22 | /**
23 | * {@inheritdoc}
24 | */
25 | public function visit(Entity $entity, GeneratorInterface $class)
26 | {
27 | $class->addMethodFromGenerator($this->generateSerializedSizeMethod($entity));
28 | }
29 |
30 | /**
31 | * @param \Protobuf\Compiler\Entity $entity
32 | *
33 | * @return string
34 | */
35 | protected function generateSerializedSizeMethod(Entity $entity)
36 | {
37 | $lines = $this->generateBody($entity);
38 | $body = implode(PHP_EOL, $lines);
39 | $method = MethodGenerator::fromArray([
40 | 'name' => 'serializedSize',
41 | 'body' => $body,
42 | 'parameters' => [
43 | [
44 | 'name' => 'context',
45 | 'type' => '\Protobuf\ComputeSizeContext'
46 | ]
47 | ],
48 | 'docblock' => [
49 | 'shortDescription' => "{@inheritdoc}"
50 | ]
51 | ]);
52 |
53 | return $method;
54 | }
55 |
56 | /**
57 | * @param \Protobuf\Compiler\Entity $entity
58 | *
59 | * @return string[]
60 | */
61 | protected function generateBody(Entity $entity)
62 | {
63 | $descriptor = $entity->getDescriptor();
64 | $fields = $descriptor->getFieldList() ?: [];
65 | $extLines = $this->generateExtensionsSerializedSize($entity);
66 |
67 | $body[] = '$calculator = $context->getSizeCalculator();';
68 | $body[] = '$size = 0;';
69 | $body[] = null;
70 |
71 | foreach ($fields as $field) {
72 | $lines = $this->generateFieldCondition($entity, $field);
73 | $body = array_merge($body, $lines, [null]);
74 | }
75 |
76 | $body = array_merge($body, $extLines);
77 | $body[] = null;
78 | $body[] = 'return $size;';
79 |
80 | return $body;
81 | }
82 |
83 | /**
84 | * @param \Protobuf\Compiler\Entity $entity
85 | *
86 | * @return string[]
87 | */
88 | protected function generateExtensionsSerializedSize(Entity $entity)
89 | {
90 | $descriptor = $entity->getDescriptor();
91 | $extensionsField = $this->getUniqueFieldName($descriptor, 'extensions');
92 |
93 | $body[] = 'if ($this->' . $extensionsField . ' !== null) {';
94 | $body[] = ' $size += $this->' . $extensionsField . '->serializedSize($context);';
95 | $body[] = '}';
96 |
97 | return $body;
98 | }
99 |
100 | /**
101 | * @param \Protobuf\Compiler\Entity $entity
102 | * @param \google\protobuf\FieldDescriptorProto $field
103 | *
104 | * @return string[]
105 | */
106 | protected function generateFieldCondition(Entity $entity, FieldDescriptorProto $field)
107 | {
108 | $sttm = $this->generateFieldSizeStatement($entity, $field);
109 | $lines = $this->addIndentation($sttm, 1);
110 | $name = $field->getName();
111 |
112 | $body[] = 'if ($this->' . $name . ' !== null) {';
113 | $body = array_merge($body, $lines);
114 | $body[] = '}';
115 |
116 | return $body;
117 | }
118 |
119 | /**
120 | * @param \Protobuf\Compiler\Entity $entity
121 | * @param \google\protobuf\FieldDescriptorProto $field
122 | *
123 | * @return string[]
124 | */
125 | protected function generateFieldSizeStatement(Entity $entity, FieldDescriptorProto $field)
126 | {
127 | $generator = new SerializedSizeFieldStatementGenerator($this->context);
128 | $statement = $generator->generateFieldSizeStatement($entity, $field);
129 |
130 | return $statement;
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Protobuf for PHP
2 | ================
3 |
4 | [](https://travis-ci.org/protobuf-php/protobuf-plugin)
5 | [](https://coveralls.io/github/protobuf-php/protobuf-plugin?branch=master)
6 |
7 | Protobuf for PHP is an implementation of Google's Protocol Buffers for the PHP
8 | language, supporting its binary data serialization and including a `protoc`
9 | plugin to generate PHP classes from ```.proto``` files.
10 |
11 |
12 | ## Installation
13 |
14 | If you wish to compile ```.proto``` definitions to PHP,
15 | you will need to install [Google's Protocol Buffers](https://github.com/google/protobuf) from your favorite package manager or from source.
16 | This plugin currently supports protobuf 2.3.0. or later.
17 |
18 | **Note**: *Google's Protocol Buffers and ```proto``` is not a runtime requirement for [protobuf-php/protobuf](https://github.com/protobuf-php/protobuf), It is only necessary if you wish to compile your definitions to PHP using [protobuf-php/protobuf-plugin](https://github.com/protobuf-php/protobuf-plugin).*
19 |
20 |
21 | #### Installing Google's Protocol Buffers
22 |
23 | * **OSX Install**
24 |
25 | ```console
26 | $ brew install protobuf
27 | ```
28 |
29 | * **Ubuntu**
30 |
31 | ```console
32 | $ sudo apt-get install -y protobuf
33 | ```
34 |
35 | Make sure you have ```protoc``` available in the user's path:
36 | ```console
37 | $ protoc --version
38 | $ # libprotoc 2.6.1
39 | ```
40 |
41 | **Note**: *For more information on how to install/compile Google's Protocol Buffers see : https://github.com/google/protobuf*
42 |
43 |
44 | #### Composer install
45 |
46 | To install the PHP plugin run the following `composer` commands:
47 |
48 | ```console
49 | $ composer require "protobuf-php/protobuf-plugin"
50 | ```
51 |
52 | #### Defining Your Protocol Format
53 |
54 | To create your address book application, you'll need to start with a ```.proto``` file. The definitions in a ```.proto``` file are simple: you add a message for each data structure you want to serialize, then specify a name and a type for each field in the message. Here is the ```.proto``` file that defines your messages, ```addressbook.proto```.
55 |
56 | ```proto
57 | package tutorial;
58 | import "php.proto";
59 | option (php.package) = "Tutorial.AddressBookProtos";
60 |
61 | message Person {
62 | required string name = 1;
63 | required int32 id = 2;
64 | optional string email = 3;
65 |
66 | enum PhoneType {
67 | MOBILE = 0;
68 | HOME = 1;
69 | WORK = 2;
70 | }
71 |
72 | message PhoneNumber {
73 | required string number = 1;
74 | optional PhoneType type = 2 [default = HOME];
75 | }
76 |
77 | repeated PhoneNumber phone = 4;
78 | }
79 |
80 | message AddressBook {
81 | repeated Person person = 1;
82 | }
83 | ```
84 |
85 | As you can see, the syntax is similar to C++ or Java. Let's go through each part of the file and see what it does.
86 | The ```.proto``` file starts with a package declaration, which helps to prevent naming conflicts between different projects.
87 | In PHP, the package name is used as the PHP namespace unless you have explicitly specified a ```(php.package)```, as we have here.
88 | Even if you do provide a ```(php.package)```, you should still define a normal package as well to avoid name collisions in the Protocol Buffers name space as well as in non PHP languages.
89 |
90 | You'll find a complete guide to writing ```.proto``` files – including all the possible field types – in the [Protocol Buffer Language Guide](https://developers.google.com/protocol-buffers/docs/proto). Don't go looking for facilities similar to class inheritance, though – protocol buffers don't do that.
91 |
92 |
93 | #### Compiling Your Protocol Buffers
94 |
95 | Now that you have a ```.proto```, the next thing you need to do is generate the classes you'll need to read and write ```AddressBook``` (and hence ```Person``` and ```PhoneNumber```) messages. To do this, you need to run the protocol buffer plugin on your ```.proto```.
96 |
97 | In this case:
98 |
99 | ```console
100 | php ./vendor/bin/protobuf --include-descriptors -i . -o ./src/ ./addressbook.proto
101 | ```
102 |
103 | This generates the following PHP classes in your specified destination directory
104 |
105 | ```console
106 | src/
107 | └── Tutorial
108 | └── AddressBookProtos
109 | ├── AddressBook.php
110 | ├── Person
111 | │ ├── PhoneNumber.php
112 | │ └── PhoneType.php
113 | └── Person.php
114 | ```
115 |
116 | **Note**: *For more information on how to use the generated code see : [protobuf-php/protobuf](https://github.com/protobuf-php/protobuf)*
117 |
--------------------------------------------------------------------------------
/src/Generator/Message/SerializedSizeFieldStatementGenerator.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | class SerializedSizeFieldStatementGenerator extends BaseGenerator
21 | {
22 | /**
23 | * @var string
24 | */
25 | protected $targetVar;
26 |
27 | /**
28 | * @param string $variable
29 | */
30 | public function setTargetVar($variable)
31 | {
32 | $this->targetVar = $variable;
33 | }
34 |
35 | /**
36 | * @param \Protobuf\Compiler\Entity $entity
37 | * @param \google\protobuf\FieldDescriptorProto $field
38 | *
39 | * @return string[]
40 | */
41 | public function generateFieldSizeStatement(Entity $entity, FieldDescriptorProto $field)
42 | {
43 | $body = [];
44 | $name = $field->getName();
45 | $type = $field->getType();
46 | $rule = $field->getLabel();
47 | $tag = $field->getNumber();
48 | $options = $field->getOptions();
49 | $variable = $this->targetVar ?: '$this->' . $name;
50 | $isPack = $options ? $options->getPacked() : false;
51 |
52 | $wire = $isPack ? WireFormat::WIRE_LENGTH : WireFormat::getWireType($type->value(), null);
53 | $key = WireFormat::getFieldKey($tag, $wire);
54 | $keySize = $this->getSizeCalculator()->computeVarintSize($key);
55 |
56 | if ($rule === Label::LABEL_REPEATED() && $isPack) {
57 | $itemValSttm = ($type === Type::TYPE_ENUM())
58 | ? '$val->value()'
59 | : '$val';
60 |
61 | $body[] = '$innerSize = 0;';
62 | $body[] = null;
63 | $body[] = 'foreach (' . $variable . ' as $val) {';
64 | $body[] = ' $innerSize += ' . $this->generateValueSizeStatement($type->value(), $itemValSttm) . ';';
65 | $body[] = '}';
66 | $body[] = null;
67 | $body[] = '$size += ' . $keySize . ';';
68 | $body[] = '$size += $innerSize;';
69 | $body[] = '$size += $calculator->computeVarintSize($innerSize);';
70 |
71 | return $body;
72 | }
73 |
74 | if ($type === Type::TYPE_MESSAGE() && $rule === Label::LABEL_REPEATED()) {
75 | $body[] = 'foreach (' . $variable . ' as $val) {';
76 | $body[] = ' $innerSize = $val->serializedSize($context);';
77 | $body[] = null;
78 | $body[] = ' $size += ' . $keySize . ';';
79 | $body[] = ' $size += $innerSize;';
80 | $body[] = ' $size += $calculator->computeVarintSize($innerSize);';
81 | $body[] = '}';
82 |
83 | return $body;
84 | }
85 |
86 | if ($type === Type::TYPE_ENUM() && $rule === Label::LABEL_REPEATED()) {
87 | $body[] = 'foreach (' . $variable . ' as $val) {';
88 | $body[] = ' $size += ' . $keySize . ';';
89 | $body[] = ' $size += ' . $this->generateValueSizeStatement($type->value(), '$val->value()') . ';';
90 | $body[] = '}';
91 |
92 | return $body;
93 | }
94 |
95 | if ($rule === Label::LABEL_REPEATED()) {
96 | $body[] = 'foreach (' . $variable . ' as $val) {';
97 | $body[] = ' $size += ' . $keySize . ';';
98 | $body[] = ' $size += ' . $this->generateValueSizeStatement($type->value(), '$val') . ';';
99 | $body[] = '}';
100 |
101 | return $body;
102 | }
103 |
104 | if ($type === Type::TYPE_ENUM()) {
105 | $body[] = '$size += ' . $keySize . ';';
106 | $body[] = '$size += ' . $this->generateValueSizeStatement($type->value(), $variable . '->value()') . ';';
107 |
108 | return $body;
109 | }
110 |
111 | if ($type !== Type::TYPE_MESSAGE()) {
112 | $body[] = '$size += ' . $keySize . ';';
113 | $body[] = '$size += ' . $this->generateValueSizeStatement($type->value(), $variable) . ';';
114 |
115 | return $body;
116 | }
117 |
118 | $body[] = '$innerSize = ' . $variable . '->serializedSize($context);';
119 | $body[] = null;
120 | $body[] = '$size += ' . $keySize . ';';
121 | $body[] = '$size += $innerSize;';
122 | $body[] = '$size += $calculator->computeVarintSize($innerSize);';
123 |
124 | return $body;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/Command/GenerateCommand.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | class GenerateCommand extends Command
19 | {
20 | /**
21 | * plugin command
22 | *
23 | * @var string
24 | */
25 | protected $plugin;
26 |
27 | /**
28 | * Constructor.
29 | *
30 | * @param string $plugin The plugin command
31 | */
32 | public function __construct($plugin)
33 | {
34 | $this->plugin = $plugin;
35 |
36 | parent::__construct('protobuf:generate');
37 | }
38 |
39 | /**
40 | * {@inheritdoc}
41 | */
42 | protected function configure()
43 | {
44 | $this
45 | ->setName('protobuf:generate')
46 | ->setDescription('Executes protoc to generate PHP classes')
47 | ->addArgument('protos', InputArgument::IS_ARRAY|InputArgument::REQUIRED, 'proto files')
48 | ->addOption('generate-imported', null, InputOption::VALUE_NONE, 'Generate imported proto files')
49 | ->addOption('protoc', null, InputOption::VALUE_REQUIRED, 'protoc compiler executable path', 'protoc')
50 | ->addOption('out', 'o', InputOption::VALUE_REQUIRED, 'destination directory for generated files', './')
51 | ->addOption('psr4', null, InputOption::VALUE_IS_ARRAY|InputOption::VALUE_REQUIRED, 'psr-4 base directory')
52 | ->addOption('include', 'i', InputOption::VALUE_IS_ARRAY|InputOption::VALUE_REQUIRED, 'define an include path')
53 | ->addOption('include-descriptors', null, InputOption::VALUE_NONE, 'add google-protobuf-proto descriptors to include path');
54 | }
55 |
56 | /**
57 | * {@inheritdoc}
58 | */
59 | protected function execute(InputInterface $input, OutputInterface $output)
60 | {
61 | $args = [];
62 | $out = $input->getOption('out');
63 | $psr4 = $input->getOption('psr4');
64 | $protoc = $input->getOption('protoc');
65 | $protos = $input->getArgument('protos');
66 | $include = $input->getOption('include') ?: [];
67 | $builder = $this->createProcessBuilder($this->plugin, $protoc);
68 |
69 | if ($output->isVerbose()) {
70 | $args['verbose'] = 1;
71 | }
72 |
73 | if ($input->getOption('generate-imported')) {
74 | $args['generate-imported'] = 1;
75 | }
76 |
77 | if ($psr4) {
78 | $args['psr4'] = $psr4;
79 | }
80 |
81 | if ($input->getOption('include-descriptors')) {
82 | $builder->setIncludeDescriptors(true);
83 | $builder->setDescriptorPaths([
84 | __DIR__ . '/../../../google-protobuf-proto/src',
85 | __DIR__ . '/../../vendor/protobuf-php/google-protobuf-proto/src'
86 | ]);
87 | }
88 |
89 | $builder->assertVersion();
90 |
91 | $process = $builder->createProcess($out, $protos, $include, $args);
92 | $command = $process->getCommandLine();
93 |
94 | if ($output->isVerbose()) {
95 | $output->writeln("Generating protos with protoc -- $command");
96 | }
97 |
98 | // Run command
99 | $process->run(function ($type, $buffer) use ($output) {
100 | if ( ! $output->isVerbose() || ! $buffer) {
101 | return;
102 | }
103 |
104 | $output->writeln($buffer);
105 | });
106 |
107 | $return = $process->getExitCode();
108 | $result = $process->getOutput();
109 |
110 | if ($return === 0) {
111 | $output->writeln("PHP classes successfully generate.");
112 |
113 | return $return;
114 | }
115 |
116 | $output->writeln('protoc exited with an error ('.$return.') when executed with: ');
117 | $output->writeln('');
118 | $output->writeln(' ' . $command);
119 | $output->writeln('');
120 | $output->writeln($result);
121 | $output->writeln('');
122 | $output->writeln($process->getErrorOutput());
123 | $output->writeln('');
124 |
125 | return $return;
126 | }
127 |
128 | /**
129 | * @param string $plugin
130 | * @param string $protoc
131 | *
132 | * @return \Protobuf\Compiler\Protoc\ProcessBuilder
133 | */
134 | protected function createProcessBuilder($plugin, $protoc)
135 | {
136 | return new ProcessBuilder($plugin, $protoc);
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/tests/Fixtures/Options/SimpleMessage.tpl:
--------------------------------------------------------------------------------
1 | extensions !== null) {
33 | return $this->extensions;
34 | }
35 |
36 | return $this->extensions = new \Protobuf\Extension\ExtensionFieldMap(__CLASS__);
37 | }
38 |
39 | /**
40 | * {@inheritdoc}
41 | */
42 | public function unknownFieldSet()
43 | {
44 | return $this->unknownFieldSet;
45 | }
46 |
47 | /**
48 | * {@inheritdoc}
49 | */
50 | public static function fromStream($stream, \Protobuf\Configuration $configuration = null)
51 | {
52 | return new self($stream, $configuration);
53 | }
54 |
55 | /**
56 | * {@inheritdoc}
57 | */
58 | public static function fromArray(array $values)
59 | {
60 | $message = new self();
61 | $values = array_merge([
62 | ], $values);
63 |
64 | return $message;
65 | }
66 |
67 | /**
68 | * {@inheritdoc}
69 | */
70 | public static function descriptor()
71 | {
72 | return \google\protobuf\DescriptorProto::fromArray([
73 | 'name' => 'SimpleMessage',
74 | ]);
75 | }
76 |
77 | /**
78 | * {@inheritdoc}
79 | */
80 | public function toStream(\Protobuf\Configuration $configuration = null)
81 | {
82 | $config = $configuration ?: \Protobuf\Configuration::getInstance();
83 | $context = $config->createWriteContext();
84 | $stream = $context->getStream();
85 |
86 | $this->writeTo($context);
87 | $stream->seek(0);
88 |
89 | return $stream;
90 | }
91 |
92 | /**
93 | * {@inheritdoc}
94 | */
95 | public function writeTo(\Protobuf\WriteContext $context)
96 | {
97 | $stream = $context->getStream();
98 | $writer = $context->getWriter();
99 | $sizeContext = $context->getComputeSizeContext();
100 |
101 | if ($this->extensions !== null) {
102 | $this->extensions->writeTo($context);
103 | }
104 |
105 | return $stream;
106 | }
107 |
108 | /**
109 | * {@inheritdoc}
110 | */
111 | public function readFrom(\Protobuf\ReadContext $context)
112 | {
113 | $reader = $context->getReader();
114 | $length = $context->getLength();
115 | $stream = $context->getStream();
116 |
117 | $limit = ($length !== null)
118 | ? ($stream->tell() + $length)
119 | : null;
120 |
121 | while ($limit === null || $stream->tell() < $limit) {
122 |
123 | if ($stream->eof()) {
124 | break;
125 | }
126 |
127 | $key = $reader->readVarint($stream);
128 | $wire = \Protobuf\WireFormat::getTagWireType($key);
129 | $tag = \Protobuf\WireFormat::getTagFieldNumber($key);
130 |
131 | if ($stream->eof()) {
132 | break;
133 | }
134 |
135 | $extensions = $context->getExtensionRegistry();
136 | $extension = $extensions ? $extensions->findByNumber(__CLASS__, $tag) : null;
137 |
138 | if ($extension !== null) {
139 | $this->extensions()->add($extension, $extension->readFrom($context, $wire));
140 |
141 | continue;
142 | }
143 |
144 | if ($this->unknownFieldSet === null) {
145 | $this->unknownFieldSet = new \Protobuf\UnknownFieldSet();
146 | }
147 |
148 | $data = $reader->readUnknown($stream, $wire);
149 | $unknown = new \Protobuf\Unknown($tag, $wire, $data);
150 |
151 | $this->unknownFieldSet->add($unknown);
152 |
153 | }
154 | }
155 |
156 | /**
157 | * {@inheritdoc}
158 | */
159 | public function serializedSize(\Protobuf\ComputeSizeContext $context)
160 | {
161 | $calculator = $context->getSizeCalculator();
162 | $size = 0;
163 |
164 | if ($this->extensions !== null) {
165 | $size += $this->extensions->serializedSize($context);
166 | }
167 |
168 | return $size;
169 | }
170 |
171 | /**
172 | * {@inheritdoc}
173 | */
174 | public function clear()
175 | {
176 | }
177 |
178 | /**
179 | * {@inheritdoc}
180 | */
181 | public function merge(\Protobuf\Message $message)
182 | {
183 | if ( ! $message instanceof \ProtobufCompilerTest\Protos\Options\SimpleMessage) {
184 | throw new \InvalidArgumentException(sprintf('Argument 1 passed to %s must be a %s, %s given', __METHOD__, __CLASS__, get_class($message)));
185 | }
186 | }
187 |
188 |
189 | }
190 |
191 |
--------------------------------------------------------------------------------
/src/Generator/ServiceGenerator.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | class ServiceGenerator extends BaseGenerator implements EntityVisitor
20 | {
21 | /**
22 | * {@inheritdoc}
23 | */
24 | public function visit(Entity $entity)
25 | {
26 | $name = $entity->getName();
27 | $namespace = $entity->getNamespace();
28 | $shortDescription = 'Protobuf service : ' . $entity->getClass();
29 | $class = InterfaceGenerator::fromArray([
30 | 'name' => $name,
31 | 'namespacename' => $namespace,
32 | 'methods' => $this->generateMethods($entity),
33 | 'docblock' => [
34 | 'shortDescription' => $shortDescription,
35 | ]
36 | ]);
37 |
38 | $entity->setContent($this->generateFileContent($class, $entity));
39 | }
40 |
41 | /**
42 | * @param \Protobuf\Compiler\Entity $entity
43 | *
44 | * @return string[]
45 | */
46 | protected function generateMethods(Entity $entity)
47 | {
48 | $result = [];
49 | $descriptor = $entity->getDescriptor();
50 | $methods = $descriptor->getMethodList() ?: [];
51 |
52 | foreach ($methods as $method) {
53 | $result[] = $this->generateMethod($entity, $method);
54 | }
55 |
56 | return $result;
57 | }
58 |
59 | /**
60 | * @param \Protobuf\Compiler\Entity $entity
61 | * @param \google\protobuf\MethodDescriptorProto $method
62 | *
63 | * @return string
64 | */
65 | protected function generateMethod(Entity $entity, MethodDescriptorProto $method)
66 | {
67 | $inputClass = $this->getMethodInputTypeHint($method);
68 | $inputDoc = $this->getMethodInputDocblock($method);
69 | $outputDoc = $this->getMethodOutputDocblock($method);
70 | $methodName = $this->getCamelizedValue($method->getName());
71 | $method = MethodGenerator::fromArray([
72 | 'name' => $methodName,
73 | 'parameters' => [
74 | [
75 | 'name' => 'input',
76 | 'type' => $inputClass
77 | ]
78 | ],
79 | 'docblock' => [
80 | 'tags' => [
81 | [
82 | 'name' => 'param',
83 | 'description' => $inputDoc . ' $input'
84 | ],
85 | [
86 | 'name' => 'return',
87 | 'description' => $outputDoc
88 | ]
89 | ]
90 | ]
91 | ]);
92 |
93 | return $method;
94 | }
95 |
96 | /**
97 | * @param \google\protobuf\MethodDescriptorProto $method
98 | *
99 | * @return string
100 | */
101 | protected function getMethodInputTypeHint(MethodDescriptorProto $method)
102 | {
103 | $refType = $method->getInputType();
104 | $refEntity = $this->getEntity($refType);
105 |
106 | if ($method->getClientStreaming()) {
107 | return '\Iterator';
108 | }
109 |
110 | return $refEntity->getNamespacedName();
111 | }
112 |
113 | /**
114 | * @param \google\protobuf\MethodDescriptorProto $method
115 | *
116 | * @return string
117 | */
118 | protected function getMethodOutputTypeHint(MethodDescriptorProto $method)
119 | {
120 | $refType = $method->getOutputType();
121 | $refEntity = $this->getEntity($refType);
122 |
123 | if ($method->getServerStreaming()) {
124 | return '\Iterator';
125 | }
126 |
127 | return $refEntity->getNamespacedName();
128 | }
129 |
130 | /**
131 | * @param \google\protobuf\MethodDescriptorProto $method
132 | *
133 | * @return string
134 | */
135 | protected function getMethodInputDocblock(MethodDescriptorProto $method)
136 | {
137 | $refType = $method->getInputType();
138 | $refEntity = $this->getEntity($refType);
139 | $refClass = $this->getMethodInputTypeHint($method);
140 |
141 | if ($method->getClientStreaming()) {
142 | return sprintf('\Iterator<%s>', $refEntity->getNamespacedName());
143 | }
144 |
145 | return $refClass;
146 | }
147 |
148 | /**
149 | * @param \google\protobuf\MethodDescriptorProto $method
150 | *
151 | * @return string
152 | */
153 | protected function getMethodOutputDocblock(MethodDescriptorProto $method)
154 | {
155 | $refType = $method->getOutputType();
156 | $refEntity = $this->getEntity($refType);
157 | $refClass = $this->getMethodOutputTypeHint($method);
158 |
159 | if ($method->getServerStreaming()) {
160 | return sprintf('\Iterator<%s>', $refEntity->getNamespacedName());
161 | }
162 |
163 | return $refClass;
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/tests/Command/GenerateCommandTest.php:
--------------------------------------------------------------------------------
1 | getMockBuilder('Protobuf\Compiler\Protoc\ProcessBuilder')
16 | ->disableOriginalConstructor()
17 | ->getMock();
18 |
19 | $process = $this->getMockBuilder('Symfony\Component\Process\Process')
20 | ->disableOriginalConstructor()
21 | ->getMock();
22 |
23 | $mock = $this->getMockBuilder(GenerateCommand::CLASS)
24 | ->setMethods(['createProcessBuilder'])
25 | ->setConstructorArgs(['./bin/protobuf'])
26 | ->getMock();
27 |
28 | $mock->expects($this->once())
29 | ->method('createProcessBuilder')
30 | ->willReturn($builder);
31 |
32 | $builder->expects($this->once())
33 | ->method('createProcess')
34 | ->willReturn($process)
35 | ->with(
36 | $this->equalTo('./src'),
37 | $this->equalTo(['./file.proto']),
38 | $this->equalTo(['path-to-include']),
39 | $this->equalTo([
40 | 'generate-imported' => 1,
41 | 'verbose' => 1,
42 | 'psr4' => ['ProtobufTest\Protos']
43 | ])
44 | );
45 |
46 | $process->expects($this->once())
47 | ->method('run');
48 |
49 | $process->expects($this->once())
50 | ->method('getExitCode')
51 | ->willReturn(0);
52 |
53 | $process->expects($this->once())
54 | ->method('getOutput')
55 | ->willReturn('OK');
56 |
57 | $process->expects($this->once())
58 | ->method('getCommandLine')
59 | ->willReturn('"protoc command"');
60 |
61 | $application = new Application();
62 |
63 | $application->add($mock);
64 |
65 | $command = $application->find('protobuf:generate');
66 | $commandTester = new CommandTester($command);
67 |
68 | $commandTester->execute([
69 | '--generate-imported' => true,
70 | '--out' => './src',
71 | 'protos' => ['./file.proto'],
72 | '--protoc' => '/usr/bin/protoc',
73 | '--include' => ['path-to-include'],
74 | '--psr4' => ['ProtobufTest\Protos'],
75 | ], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE]);
76 |
77 | $this->assertContains('Generating protos with protoc -- "protoc command"', $commandTester->getDisplay());
78 | $this->assertContains('PHP classes successfully generate.', $commandTester->getDisplay());
79 | }
80 |
81 | public function testExecuteFail()
82 | {
83 | $builder = $this->getMockBuilder('Protobuf\Compiler\Protoc\ProcessBuilder')
84 | ->disableOriginalConstructor()
85 | ->getMock();
86 |
87 | $process = $this->getMockBuilder('Symfony\Component\Process\Process')
88 | ->disableOriginalConstructor()
89 | ->getMock();
90 |
91 | $mock = $this->getMockBuilder(GenerateCommand::CLASS)
92 | ->setMethods(['createProcessBuilder'])
93 | ->setConstructorArgs(['./bin/protobuf-plugin'])
94 | ->getMock();
95 |
96 | $mock->expects($this->once())
97 | ->method('createProcessBuilder')
98 | ->willReturn($builder);
99 |
100 | $builder->expects($this->once())
101 | ->method('createProcess')
102 | ->willReturn($process)
103 | ->with(
104 | $this->equalTo('./'),
105 | $this->equalTo(['./file.proto']),
106 | $this->equalTo([]),
107 | $this->equalTo([])
108 | );
109 |
110 | $process->expects($this->once())
111 | ->method('run');
112 |
113 | $process->expects($this->once())
114 | ->method('getExitCode')
115 | ->willReturn(255);
116 |
117 | $process->expects($this->once())
118 | ->method('getOutput')
119 | ->willReturn('Fail');
120 |
121 | $process->expects($this->once())
122 | ->method('getCommandLine')
123 | ->willReturn('"protoc command"');
124 |
125 | $application = new Application();
126 |
127 | $application->add($mock);
128 |
129 | $command = $application->find('protobuf:generate');
130 | $commandTester = new CommandTester($command);
131 |
132 | $commandTester->execute([
133 | 'protos' => ['./file.proto']
134 | ]);
135 |
136 | $this->assertContains('protoc exited with an error (255) when executed', $commandTester->getDisplay());
137 | }
138 |
139 | public function testCreateProcessBuilder()
140 | {
141 | $command = new GenerateCommand('./bin/protobuf-plugin');
142 | $builder = $this->invokeMethod($command, 'createProcessBuilder', ['./bin/protobuf-plugin', '2.3.1']);
143 |
144 | $this->assertInstanceOf('Protobuf\Compiler\Protoc\ProcessBuilder', $builder);
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/Generator/Message/WriteToGenerator.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | class WriteToGenerator extends BaseGenerator implements GeneratorVisitor
24 | {
25 | /**
26 | * {@inheritdoc}
27 | */
28 | public function visit(Entity $entity, GeneratorInterface $class)
29 | {
30 | $class->addMethodFromGenerator($this->generateWriteToMethod($entity));
31 | }
32 |
33 | /**
34 | * @param \Protobuf\Compiler\Entity $entity
35 | *
36 | * @return \Zend\Code\Generator\GeneratorInterface
37 | */
38 | protected function generateWriteToMethod(Entity $entity)
39 | {
40 | $lines = $this->generateBody($entity);
41 | $body = implode(PHP_EOL, $lines);
42 | $method = MethodGenerator::fromArray([
43 | 'name' => 'writeTo',
44 | 'body' => $body,
45 | 'parameters' => [
46 | [
47 | 'name' => 'context',
48 | 'type' => '\Protobuf\WriteContext',
49 | ]
50 | ],
51 | 'docblock' => [
52 | 'shortDescription' => "{@inheritdoc}"
53 | ]
54 | ]);
55 |
56 | return $method;
57 | }
58 |
59 | /**
60 | * @param \Protobuf\Compiler\Entity $entity
61 | *
62 | * @return string[]
63 | */
64 | protected function generateBody(Entity $entity)
65 | {
66 | $descriptor = $entity->getDescriptor();
67 | $fields = $descriptor->getFieldList() ?: [];
68 |
69 | $body[] = '$stream = $context->getStream();';
70 | $body[] = '$writer = $context->getWriter();';
71 | $body[] = '$sizeContext = $context->getComputeSizeContext();';
72 | $body[] = null;
73 |
74 | foreach ($fields as $field) {
75 | $lines = $this->generateRequiredFieldException($entity, $field);
76 | $body = array_merge($body, $lines);
77 | }
78 |
79 | foreach ($fields as $field) {
80 | $lines = $this->generateFieldCondition($entity, $field);
81 | $body = array_merge($body, $lines, [null]);
82 | }
83 |
84 | $extensionsField = $this->getUniqueFieldName($descriptor, 'extensions');
85 | $extensionsVar = '$this->' . $extensionsField;
86 |
87 | $body[] = 'if (' . $extensionsVar . ' !== null) {';
88 | $body[] = ' ' . $extensionsVar . '->writeTo($context);';
89 | $body[] = '}';
90 | $body[] = null;
91 | $body[] = 'return $stream;';
92 |
93 | return $body;
94 | }
95 |
96 | /**
97 | * @param \Protobuf\Compiler\Entity $entity
98 | * @param \google\protobuf\FieldDescriptorProto $field
99 | *
100 | * @return string[]
101 | */
102 | protected function generateRequiredFieldException(Entity $entity, FieldDescriptorProto $field)
103 | {
104 | $name = $field->getName();
105 | $label = $field->getLabel();
106 | $tag = $field->getNumber();
107 | $isRequired = $label === Label::LABEL_REQUIRED();
108 |
109 | if ( ! $isRequired) {
110 | return [];
111 | }
112 |
113 | $class = $entity->getNamespacedName();
114 | $format = 'Field "%s#%s" (tag %s) is required but has no value.';
115 | $message = var_export(sprintf($format, $class, $name, $tag), true);
116 |
117 | $body[] = 'if ($this->' . $name . ' === null) {';
118 | $body[] = ' throw new \UnexpectedValueException(' . $message . ');';
119 | $body[] = '}';
120 | $body[] = null;
121 |
122 | return $body;
123 | }
124 |
125 | /**
126 | * @param \Protobuf\Compiler\Entity $entity
127 | * @param \google\protobuf\FieldDescriptorProto $field
128 | *
129 | * @return string[]
130 | */
131 | protected function generateFieldCondition(Entity $entity, FieldDescriptorProto $field)
132 | {
133 | $body = [];
134 | $fieldName = $field->getName();
135 | $format = 'if ($this->%s !== null) {';
136 | $sttm = $this->generateFieldWriteStatement($entity, $field);
137 | $lines = $this->addIndentation($sttm, 1);
138 |
139 | $body[] = sprintf($format, $fieldName);
140 | $body = array_merge($body, $lines);
141 | $body[] = '}';
142 |
143 | return $body;
144 | }
145 |
146 | /**
147 | * @param \Protobuf\Compiler\Entity $entity
148 | * @param google\protobuf\FieldDescriptorProto $field
149 | *
150 | * @return string[]
151 | */
152 | protected function generateFieldWriteStatement(Entity $entity, FieldDescriptorProto $field)
153 | {
154 | $generator = new WriteFieldStatementGenerator($this->context);
155 | $statement = $generator->generateFieldWriteStatement($entity, $field);
156 |
157 | return $statement;
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/tests/Descriptor/SerializedSizeTest.php:
--------------------------------------------------------------------------------
1 | markTestIncompleteIfProtoClassNotFound();
24 |
25 | parent::setUp();
26 | }
27 |
28 | public function testSimpleSerializedSize()
29 | {
30 | $simple = new Simple();
31 | $calculator = $this->getMock(SizeCalculator::CLASS, [], [], '', false);
32 | $context = $this->getMock(ComputeSizeContext::CLASS, [], [], '', false);
33 |
34 | $simple->setBool(true);
35 | $simple->setBytes("bar");
36 | $simple->setString("foo");
37 | $simple->setFloat(12345.123);
38 | $simple->setUint32(123456789);
39 | $simple->setInt32(-123456789);
40 | $simple->setFixed32(123456789);
41 | $simple->setSint32(-123456789);
42 | $simple->setSfixed32(-123456789);
43 | $simple->setDouble(123456789.12345);
44 | $simple->setInt64(-123456789123456789);
45 | $simple->setUint64(123456789123456789);
46 | $simple->setFixed64(123456789123456789);
47 | $simple->setSint64(-123456789123456789);
48 | $simple->setSfixed64(-123456789123456789);
49 |
50 | $context->expects($this->once())
51 | ->method('getSizeCalculator')
52 | ->willReturn($calculator);
53 |
54 | $calculator->expects($this->exactly(4))
55 | ->method('computeVarintSize')
56 | ->will($this->returnValueMap([
57 | [-123456789123456789, 10],
58 | [123456789123456789, 9],
59 | [-123456789, 10],
60 | [123456789, 4],
61 | ]));
62 |
63 | $calculator->expects($this->once())
64 | ->method('computeStringSize')
65 | ->with('foo')
66 | ->willReturn(3);
67 |
68 | $calculator->expects($this->once())
69 | ->method('computeByteStreamSize')
70 | ->with('bar')
71 | ->willReturn(3);
72 |
73 | $calculator->expects($this->once())
74 | ->method('computeZigzag32Size')
75 | ->with(-123456789)
76 | ->willReturn(4);
77 |
78 | $calculator->expects($this->once())
79 | ->method('computeZigzag64Size')
80 | ->with(-123456789123456789)
81 | ->willReturn(9);
82 |
83 | $simple->serializedSize($context);
84 | }
85 |
86 | public function testRepeatedStringSerializedSize()
87 | {
88 | $repeated = new Repeated();
89 | $calculator = $this->getMock(SizeCalculator::CLASS, [], [], '', false);
90 | $context = $this->getMock(ComputeSizeContext::CLASS, [], [], '', false);
91 |
92 | $repeated->addString('one');
93 | $repeated->addString('two');
94 | $repeated->addString('three');
95 |
96 | $context->expects($this->once())
97 | ->method('getSizeCalculator')
98 | ->willReturn($calculator);
99 |
100 | $calculator->expects($this->exactly(3))
101 | ->method('computeStringSize')
102 | ->will($this->returnValueMap([
103 | ['one', 4],
104 | ['two', 4],
105 | ['three', 6]
106 | ]));
107 |
108 | $repeated->serializedSize($context);
109 | }
110 |
111 | public function testRepeatedInt32SerializedSize()
112 | {
113 | $repeated = new Repeated();
114 | $calculator = $this->getMock(SizeCalculator::CLASS, [], [], '', false);
115 | $context = $this->getMock(ComputeSizeContext::CLASS, [], [], '', false);
116 |
117 | $repeated->addInt(1);
118 | $repeated->addInt(2);
119 | $repeated->addInt(2);
120 |
121 | $context->expects($this->once())
122 | ->method('getSizeCalculator')
123 | ->willReturn($calculator);
124 |
125 | $calculator->expects($this->exactly(3))
126 | ->method('computeVarintSize')
127 | ->will($this->returnValueMap([
128 | [1, 1],
129 | [2, 1],
130 | [3, 1]
131 | ]));
132 |
133 | $repeated->serializedSize($context);
134 | }
135 |
136 | public function testAddressBookWithExtensionsSerializedSize()
137 | {
138 | $message = new AddressBook();
139 | $person = $this->getMock(Person::CLASS);
140 | $calculator = $this->getMock(SizeCalculator::CLASS, [], [], '', false);
141 | $extensions = $this->getMock(ExtensionFieldMap::CLASS, [], [], '', false);
142 | $context = $this->getMock(ComputeSizeContext::CLASS, [], [], '', false);
143 | $personSize = 2;
144 | $extSize = 4;
145 |
146 | $message->addPerson($person);
147 | $this->setPropertyValue($message, 'extensions', $extensions);
148 |
149 | $context->expects($this->once())
150 | ->method('getSizeCalculator')
151 | ->willReturn($calculator);
152 |
153 | $calculator->expects($this->once())
154 | ->method('computeVarintSize')
155 | ->with($this->equalTo($personSize))
156 | ->willReturn(1);
157 |
158 | $extensions->expects($this->once())
159 | ->method('serializedSize')
160 | ->willReturn($extSize)
161 | ->with($context);
162 |
163 | $person->expects($this->once())
164 | ->method('serializedSize')
165 | ->willReturn($personSize)
166 | ->with($context);
167 |
168 | $message->serializedSize($context);
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/src/Generator/Message/DescriptorGenerator.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | class DescriptorGenerator extends BaseGenerator implements GeneratorVisitor
21 | {
22 | /**
23 | * {@inheritdoc}
24 | */
25 | public function visit(Entity $entity, GeneratorInterface $class)
26 | {
27 | $class->addMethodFromGenerator($this->generateMethod($entity));
28 | }
29 |
30 |
31 | /**
32 | * @param \Protobuf\Compiler\Entity $entity
33 | *
34 | * @return string[]
35 | */
36 | protected function generateMethod(Entity $entity)
37 | {
38 | $lines = $this->generateBody($entity);
39 | $body = implode(PHP_EOL, $lines);
40 | $method = MethodGenerator::fromArray([
41 | 'name' => 'descriptor',
42 | 'body' => $body,
43 | 'static' => true,
44 | 'docblock' => [
45 | 'shortDescription' => "{@inheritdoc}"
46 | ]
47 | ]);
48 |
49 | return $method;
50 | }
51 |
52 | /**
53 | * @param \Protobuf\Compiler\Entity $entity
54 | *
55 | * @return string[]
56 | */
57 | protected function generateBody(Entity $entity)
58 | {
59 | $lines = [];
60 | $fields = [];
61 | $extensions = [];
62 | $descriptor = $entity->getDescriptor();
63 |
64 | if ($descriptor->hasOptions()) {
65 | $lines = $this->createOptionsBody($entity);
66 | $lines[] = null;
67 | }
68 |
69 | foreach (($descriptor->getFieldList() ?: []) as $field) {
70 | $values = $this->generateFieldBody($field);
71 | $fields = array_merge($fields, $this->addIndentation($values, 2));
72 | }
73 |
74 | foreach (($descriptor->getExtensionList() ?: []) as $field) {
75 | $values = $this->generateFieldBody($field);
76 | $extensions = array_merge($extensions, $this->addIndentation($values, 2));
77 | }
78 |
79 | $lines[] = "return \google\protobuf\DescriptorProto::fromArray([";
80 | $lines[] = " 'name' => " . var_export($descriptor->getName(), true) . ",";
81 |
82 | if ( ! empty($fields)) {
83 | $lines[] = " 'field' => [";
84 | $lines = array_merge($lines, $fields);
85 | $lines[] = " ],";
86 | }
87 |
88 | if ( ! empty($extensions)) {
89 | $lines[] = " 'extension' => [";
90 | $lines = array_merge($lines, $extensions);
91 | $lines[] = " ],";
92 | }
93 |
94 | if ($descriptor->hasOptions()) {
95 | $lines[] = " 'options' => \$options";
96 | }
97 |
98 | $lines[] = "]);";
99 |
100 | return $lines;
101 | }
102 |
103 | /**
104 | * @param \google\protobuf\FieldDescriptorProto $field
105 | *
106 | * @return string[]
107 | */
108 | protected function generateFieldBody(FieldDescriptorProto $field)
109 | {
110 | $lines = [];
111 | $name = $field->getName();
112 | $number = $field->getNumber();
113 | $typeName = $field->getTypeName();
114 | $extendee = $field->getExtendee();
115 | $type = $field->getType()->name();
116 | $label = $field->getLabel()->name();
117 | $default = $this->getDefaultFieldValue($field);
118 | $values = [
119 | 'number' => var_export($number, true),
120 | 'name' => var_export($name, true),
121 | 'type' => '\google\protobuf\FieldDescriptorProto\Type::' . $type . '()',
122 | 'label' => '\google\protobuf\FieldDescriptorProto\Label::' . $label . '()',
123 | ];
124 |
125 | if ($extendee) {
126 | $values['extendee'] = var_export($extendee, true);
127 | }
128 |
129 | if ($typeName) {
130 | $values['type_name'] = var_export($typeName, true);
131 | }
132 |
133 | if ($field->hasDefaultValue()) {
134 | $values['default_value'] = $default;
135 | }
136 |
137 | $lines[] = '\google\protobuf\FieldDescriptorProto::fromArray([';
138 | $lines = array_merge($lines, $this->generateArrayLines($values));
139 | $lines[] = ']),';
140 |
141 | return $lines;
142 | }
143 |
144 | /**
145 | * @param \Protobuf\Compiler\Entity $entity
146 | *
147 | * @return string[]
148 | */
149 | protected function createOptionsBody(Entity $entity)
150 | {
151 | $descriptor = $entity->getDescriptor();
152 | $values = [];
153 | $lines = [];
154 |
155 | $options = $descriptor->getOptions();
156 | $extensions = $options->extensions();
157 |
158 | $lines[] = '$options = \google\protobuf\MessageOptions::fromArray([';
159 | $lines = array_merge($lines, $this->generateArrayLines($values));
160 | $lines[] = ']);';
161 | $lines[] = null;
162 |
163 | for ($extensions->rewind(); $extensions->valid(); $extensions->next()) {
164 | $extension = $extensions->current();
165 | $info = $extensions->getInfo();
166 |
167 | $method = '\\' . $extension->getMethod();
168 | $value = var_export($info, true);
169 | $name = $extension->getName();
170 |
171 | if (is_object($info)) {
172 | $value = '\\' . $value;
173 | }
174 |
175 | $lines[] = '$options->extensions()->add(' . $method . '(), ' . $value . ');';
176 | }
177 |
178 | return $lines;
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/src/Generator/Message/ReadFromGenerator.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | class ReadFromGenerator extends BaseGenerator implements GeneratorVisitor
26 | {
27 | /**
28 | * {@inheritdoc}
29 | */
30 | public function visit(Entity $entity, GeneratorInterface $class)
31 | {
32 | $class->addMethodFromGenerator($this->generateMethod($entity));
33 | }
34 |
35 | /**
36 | * @param \Protobuf\Compiler\Entity $entity
37 | *
38 | * @return \Zend\Code\Generator\GeneratorInterface
39 | */
40 | protected function generateMethod(Entity $entity)
41 | {
42 | $lines = $this->generateBody($entity);
43 | $body = implode(PHP_EOL, $lines);
44 | $method = MethodGenerator::fromArray([
45 | 'name' => 'readFrom',
46 | 'body' => $body,
47 | 'parameters' => [
48 | [
49 | 'name' => 'context',
50 | 'type' => '\Protobuf\ReadContext',
51 | ]
52 | ],
53 | 'docblock' => [
54 | 'shortDescription' => "{@inheritdoc}"
55 | ]
56 | ]);
57 |
58 | return $method;
59 | }
60 |
61 | /**
62 | * @param \Protobuf\Compiler\Entity $entity
63 | *
64 | * @return string[]
65 | */
66 | public function generateBody(Entity $entity)
67 | {
68 | $innerLoop = $this->addIndentation($this->generateInnerLoop($entity), 1);
69 |
70 | $body[] = '$reader = $context->getReader();';
71 | $body[] = '$length = $context->getLength();';
72 | $body[] = '$stream = $context->getStream();';
73 | $body[] = null;
74 | $body[] = '$limit = ($length !== null)';
75 | $body[] = ' ? ($stream->tell() + $length)';
76 | $body[] = ' : null;';
77 | $body[] = null;
78 | $body[] = 'while ($limit === null || $stream->tell() < $limit) {';
79 |
80 | $body = array_merge($body, $innerLoop);
81 |
82 | $body[] = null;
83 | $body[] = '}';
84 |
85 | return $body;
86 | }
87 |
88 | /**
89 | * @param \Protobuf\Compiler\Entity $entity
90 | *
91 | * @return string[]
92 | */
93 | protected function generateInnerLoop(Entity $entity)
94 | {
95 | $descriptor = $entity->getDescriptor();
96 | $fields = $descriptor->getFieldList() ?: [];
97 |
98 | $body[] = null;
99 | $body[] = 'if ($stream->eof()) {';
100 | $body[] = ' break;';
101 | $body[] = '}';
102 | $body[] = null;
103 | $body[] = '$key = $reader->readVarint($stream);';
104 | $body[] = '$wire = \Protobuf\WireFormat::getTagWireType($key);';
105 | $body[] = '$tag = \Protobuf\WireFormat::getTagFieldNumber($key);';
106 | $body[] = null;
107 | $body[] = 'if ($stream->eof()) {';
108 | $body[] = ' break;';
109 | $body[] = '}';
110 | $body[] = null;
111 |
112 | foreach ($fields as $field) {
113 | $lines = $this->generateFieldCondition($entity, $field);
114 | $body = array_merge($body, $lines);
115 | }
116 |
117 | $unknowFieldName = $this->getUniqueFieldName($descriptor, 'unknownFieldSet');
118 | $extensionsFieldName = $this->getUniqueFieldName($descriptor, 'extensions');
119 |
120 | $body[] = '$extensions = $context->getExtensionRegistry();';
121 | $body[] = '$extension = $extensions ? $extensions->findByNumber(__CLASS__, $tag) : null;';
122 | $body[] = null;
123 | $body[] = 'if ($extension !== null) {';
124 | $body[] = ' $this->extensions()->add($extension, $extension->readFrom($context, $wire));';
125 | $body[] = null;
126 | $body[] = ' continue;';
127 | $body[] = '}';
128 | $body[] = null;
129 | $body[] = 'if ($this->' . $unknowFieldName . ' === null) {';
130 | $body[] = ' $this->' . $unknowFieldName . ' = new \Protobuf\UnknownFieldSet();';
131 | $body[] = '}';
132 | $body[] = null;
133 | $body[] = '$data = $reader->readUnknown($stream, $wire);';
134 | $body[] = '$unknown = new \Protobuf\Unknown($tag, $wire, $data);';
135 | $body[] = null;
136 | $body[] = '$this->' . $unknowFieldName . '->add($unknown);';
137 |
138 | return $body;
139 | }
140 |
141 | /**
142 | * @param \Protobuf\Compiler\Entity $entity
143 | * @param google\protobuf\FieldDescriptorProto $field
144 | *
145 | * @return string[]
146 | */
147 | protected function generateFieldCondition(Entity $entity, FieldDescriptorProto $field)
148 | {
149 | $tag = $field->getNumber();
150 | $lines = $this->generateFieldReadStatement($entity, $field);
151 | $lines = $this->addIndentation($lines, 1);
152 |
153 | $body[] = 'if ($tag === ' . $tag . ') {';
154 | $body = array_merge($body, $lines);
155 | $body[] = '}';
156 | $body[] = null;
157 |
158 | return $body;
159 | }
160 |
161 | /**
162 | * @param \Protobuf\Compiler\Entity $entity
163 | * @param \google\protobuf\FieldDescriptorProto $field
164 | *
165 | * @return string[]
166 | */
167 | protected function generateFieldReadStatement(Entity $entity, FieldDescriptorProto $field)
168 | {
169 | $generator = new ReadFieldStatementGenerator($this->context);
170 | $statement = $generator->generateFieldReadStatement($entity, $field);
171 |
172 | return $statement;
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/src/Compiler.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | class Compiler
25 | {
26 | /**
27 | * @var \Psr\Log\LoggerInterface
28 | */
29 | protected $logger;
30 |
31 | /**
32 | * @var \Protobuf\Configuration
33 | */
34 | protected $config;
35 |
36 | /**
37 | * @param \Psr\Log\LoggerInterface $logger
38 | * @param \Protobuf\Configuration $config
39 | */
40 | public function __construct(LoggerInterface $logger, Configuration $config = null)
41 | {
42 | $this->logger = $logger;
43 | $this->config = $config ?: self::defaultConfig();
44 | }
45 |
46 | /**
47 | * @param \Protobuf\Stream $stream
48 | *
49 | * @return \Protobuf\Stream
50 | */
51 | public function compile(Stream $stream)
52 | {
53 | // Parse the request
54 | $request = CodeGeneratorRequest::fromStream($stream, $this->config);
55 | $response = new CodeGeneratorResponse();
56 | $context = $this->createContext($request);
57 | $entities = $context->getEntities();
58 | $options = $context->getOptions();
59 | $generator = new Generator($context);
60 |
61 | // whether or not it will renegerate classes with new extensions
62 | $regenerate = false;
63 | $hasExtension = $context->hasProtobufExtension();
64 |
65 | // Run each entity
66 | foreach ($entities as $key => $entity) {
67 | $generateImported = $options->getGenerateImported();
68 | $isFileToGenerate = $entity->isFileToGenerate();
69 |
70 | // Only compile those given to generate, not the imported ones
71 | if ( ! $generateImported && ! $isFileToGenerate) {
72 | $this->logger->debug(sprintf('Skipping generation of imported class "%s"', $entity->getClass()));
73 |
74 | continue;
75 | }
76 |
77 | $this->logger->info(sprintf('Generating class "%s"', $entity->getClass()));
78 |
79 | $generator->visit($entity);
80 |
81 | $file = new File();
82 | $path = $entity->getPath();
83 | $content = $entity->getContent();
84 |
85 | $file->setName($path);
86 | $file->setContent($content);
87 |
88 | $response->addFile($file);
89 |
90 | if ($hasExtension && $this->loadEntityClass($entity)) {
91 | $regenerate = true;
92 | }
93 | }
94 |
95 | if ($regenerate) {
96 |
97 | $this->logger->info('Regenerating classes with new extensions');
98 |
99 | $stream->seek(0);
100 |
101 | // Renegerate classes with new extensions
102 | return $this->compile($stream);
103 | }
104 |
105 | $this->logger->info('Generation completed.');
106 |
107 | // Finally serialize the response object
108 | return $response->toStream($this->config);
109 | }
110 |
111 | /**
112 | * @param \Protobuf\Compiler\Entity $entity
113 | *
114 | * @return bool
115 | */
116 | protected function loadEntityClass(Entity $entity)
117 | {
118 | $type = $entity->getType();
119 | $content = $entity->getContent();
120 | $class = $entity->getNamespacedName();
121 |
122 | if (class_exists($class) || interface_exists($class)) {
123 | return false;
124 | }
125 |
126 | $this->logger->info(sprintf('Loading class "%s"', $class));
127 |
128 | $tempname = tempnam(sys_get_temp_dir(), 'proto') . '.php';
129 |
130 | file_put_contents($tempname, $content);
131 |
132 | include($tempname);
133 | @unlink($tempname);
134 |
135 | if ($type === Entity::TYPE_EXTENSION) {
136 |
137 | $this->logger->info(sprintf('Registering extension "%s"', $class));
138 |
139 | $config = $this->config;
140 | $registry = $config->getExtensionRegistry();
141 |
142 | $class::registerAllExtensions($registry);
143 |
144 | return true;
145 | }
146 |
147 | return false;
148 | }
149 |
150 | /**
151 | * @param \google\protobuf\compiler\CodeGeneratorRequest $request
152 | *
153 | * @return \Protobuf\Compiler\Context
154 | */
155 | public function createContext(CodeGeneratorRequest $request)
156 | {
157 | $options = $this->createOptions($request);
158 | $entities = $this->createEntities($request);
159 | $context = new Context($entities, $options, $this->config);
160 |
161 | return $context;
162 | }
163 |
164 | /**
165 | * @param \google\protobuf\compiler\CodeGeneratorRequest $request
166 | *
167 | * @return array
168 | */
169 | protected function createEntities(CodeGeneratorRequest $request)
170 | {
171 | $builder = new EntityBuilder($request);
172 | $entities = $builder->buildEntities();
173 |
174 | return $entities;
175 | }
176 |
177 | /**
178 | * @param \google\protobuf\compiler\CodeGeneratorRequest $request
179 | *
180 | * @return \Protobuf\Compiler\Options
181 | */
182 | protected function createOptions(CodeGeneratorRequest $request)
183 | {
184 | $parameter = $request->getParameter();
185 | $options = [];
186 |
187 | parse_str($parameter, $options);
188 |
189 | return Options::fromArray($options);
190 | }
191 |
192 | /**
193 | * @return \Protobuf\Configuration
194 | */
195 | public static function defaultConfig()
196 | {
197 | $config = Configuration::getInstance();
198 | $registry = $config->getExtensionRegistry();
199 |
200 | Extension::registerAllExtensions($registry);
201 |
202 | return $config;
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/src/Entity.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class Entity
15 | {
16 | const TYPE_EXTENSION = 'extension';
17 | const TYPE_MESSAGE = 'message';
18 | const TYPE_SERVICE = 'service';
19 | const TYPE_ENUM = 'enum';
20 |
21 | /**
22 | * @var string
23 | */
24 | protected $type;
25 |
26 | /**
27 | * @var string
28 | */
29 | protected $path;
30 |
31 | /**
32 | * @var string
33 | */
34 | protected $name;
35 |
36 | /**
37 | * @var string
38 | */
39 | protected $parent;
40 |
41 | /**
42 | * @var string
43 | */
44 | protected $content;
45 |
46 | /**
47 | * @var \Protobuf\Message
48 | */
49 | protected $descriptor;
50 |
51 | /**
52 | * @var \google\protobuf\FileDescriptorProto
53 | */
54 | protected $fileDescriptor;
55 |
56 | /**
57 | * @var bool
58 | */
59 | protected $fileToGenerate = false;
60 |
61 | /**
62 | * @var bool
63 | */
64 | protected $protobufExtension = false;
65 |
66 | /**
67 | * @param string $type
68 | * @param string $name
69 | * @param \Protobuf\Message $descriptor
70 | * @param \google\protobuf\FileDescriptorProto $fileDescriptor
71 | * @param string $parent
72 | */
73 | public function __construct($type, $name, Message $descriptor, FileDescriptorProto $fileDescriptor, $parent = null)
74 | {
75 | $this->type = $type;
76 | $this->name = $name;
77 | $this->parent = $parent;
78 | $this->descriptor = $descriptor;
79 | $this->fileDescriptor = $fileDescriptor;
80 | }
81 |
82 | /**
83 | * @return string
84 | */
85 | public function getPath()
86 | {
87 | return $this->path;
88 | }
89 |
90 | /**
91 | * @param string $path
92 | */
93 | public function setPath($path)
94 | {
95 | $this->path = $path;
96 | }
97 |
98 | /**
99 | * @return string
100 | */
101 | public function getClass()
102 | {
103 | $name = $this->getName();
104 | $package = $this->getPackage();
105 |
106 | return$this->fullyQualifiedName($package, $name);
107 | }
108 |
109 | /**
110 | * @return string
111 | */
112 | public function getName()
113 | {
114 | return $this->name;
115 | }
116 |
117 | /**
118 | * @return string
119 | */
120 | public function getPackage()
121 | {
122 | $parent = $this->parent;
123 | $package = $this->fileDescriptor->getPackage();
124 |
125 | return $this->fullyQualifiedName($package, $parent);
126 | }
127 |
128 | /**
129 | * @return string
130 | */
131 | public function getNamespace()
132 | {
133 | $package = $this->getPackage();
134 | $extension = Extension::package();
135 | $extensions = $this->getFileOptionsExtensions();
136 |
137 | if ($extensions !== null && $extensions->offsetExists($extension)) {
138 | $package = $this->fullyQualifiedName($extensions->get($extension), $this->parent);
139 | }
140 |
141 | if ($package === null) {
142 | return $package;
143 | }
144 |
145 | return str_replace('.', '\\', $package);
146 | }
147 |
148 | /**
149 | * @return string
150 | */
151 | public function getNamespacedName()
152 | {
153 | $namespace = $this->getNamespace();
154 | $name = $this->getName();
155 |
156 | if ($namespace === null) {
157 | return '\\' . $name;
158 | }
159 |
160 | return '\\' . $namespace . '\\' . $name;
161 | }
162 |
163 | /**
164 | * @return string
165 | */
166 | public function getType()
167 | {
168 | return $this->type;
169 | }
170 |
171 | /**
172 | * @return string
173 | */
174 | public function getContent()
175 | {
176 | return $this->content;
177 | }
178 |
179 | /**
180 | * @param string $content
181 | */
182 | public function setContent($content)
183 | {
184 | $this->content = $content;
185 | }
186 |
187 | /**
188 | * @return bool
189 | */
190 | public function isFileToGenerate()
191 | {
192 | return $this->fileToGenerate;
193 | }
194 |
195 | /**
196 | * @param bool $flag
197 | */
198 | public function setFileToGenerate($flag)
199 | {
200 | $this->fileToGenerate = $flag;
201 | }
202 |
203 | /**
204 | * @return bool
205 | */
206 | public function isProtobufExtension()
207 | {
208 | return $this->protobufExtension;
209 | }
210 |
211 | /**
212 | * @param bool $flag
213 | */
214 | public function setProtobufExtension($flag)
215 | {
216 | $this->protobufExtension = $flag;
217 | }
218 |
219 | /**
220 | * @return \Protobuf\Message
221 | */
222 | public function getDescriptor()
223 | {
224 | return $this->descriptor;
225 | }
226 |
227 | /**
228 | * @return \google\protobuf\FileDescriptorProto
229 | */
230 | public function getFileDescriptor()
231 | {
232 | return $this->fileDescriptor;
233 | }
234 |
235 | /**
236 | * @return \Protobuf\Extension\ExtensionFieldMap
237 | */
238 | protected function getFileOptionsExtensions()
239 | {
240 | if ( ! $this->fileDescriptor->hasOptions()) {
241 | return null;
242 | }
243 |
244 | return $this->fileDescriptor->getOptions()->extensions();
245 | }
246 |
247 | /**
248 | * @return string
249 | */
250 | protected function fullyQualifiedName()
251 | {
252 | $args = func_get_args();
253 | $parts = array_filter($args);
254 | $name = implode('.', $parts);
255 |
256 | if (empty($parts)) {
257 | return null;
258 | }
259 |
260 | return $name;
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/tests/EntityTest.php:
--------------------------------------------------------------------------------
1 | assertNull($this->invokeMethod($entity, 'fullyQualifiedName', [null]));
24 | $this->assertEquals('package.SimpleMessage', $this->invokeMethod($entity, 'fullyQualifiedName', ['package', null, 'SimpleMessage']));
25 | $this->assertEquals('package.Parent.SimpleMessage', $this->invokeMethod($entity, 'fullyQualifiedName', ['package', 'Parent', 'SimpleMessage']));
26 | }
27 |
28 | public function testGetPackage()
29 | {
30 | $name = 'SimpleMessage';
31 | $type = Entity::TYPE_MESSAGE;
32 | $descriptor = new DescriptorProto();
33 | $fileDescriptor1 = new FileDescriptorProto();
34 | $fileDescriptor2 = new FileDescriptorProto();
35 | $fileDescriptor3 = new FileDescriptorProto();
36 |
37 | $fileDescriptor2->setPackage('package');
38 | $fileDescriptor3->setPackage('package');
39 |
40 | $entity1 = new Entity($type, $name, $descriptor, $fileDescriptor1);
41 | $entity2 = new Entity($type, $name, $descriptor, $fileDescriptor2);
42 | $entity3 = new Entity($type, $name, $descriptor, $fileDescriptor3, 'Parent');
43 |
44 | $this->assertNull($entity1->getPackage());
45 | $this->assertEquals('package', $entity2->getPackage());
46 | $this->assertEquals('package.Parent', $entity3->getPackage());
47 | }
48 |
49 | public function testGetClass()
50 | {
51 | $name = 'SimpleMessage';
52 | $type = Entity::TYPE_MESSAGE;
53 | $descriptor = new DescriptorProto();
54 | $fileDescriptor1 = new FileDescriptorProto();
55 | $fileDescriptor2 = new FileDescriptorProto();
56 | $fileDescriptor3 = new FileDescriptorProto();
57 |
58 | $fileDescriptor2->setPackage('package');
59 | $fileDescriptor3->setPackage('package');
60 |
61 | $entity1 = new Entity($type, $name, $descriptor, $fileDescriptor1);
62 | $entity2 = new Entity($type, $name, $descriptor, $fileDescriptor2);
63 | $entity3 = new Entity($type, $name, $descriptor, $fileDescriptor3, 'Parent');
64 |
65 | $this->assertEquals('SimpleMessage', $entity1->getClass());
66 | $this->assertEquals('package.SimpleMessage', $entity2->getClass());
67 | $this->assertEquals('package.Parent.SimpleMessage', $entity3->getClass());
68 | }
69 |
70 | public function testGetNamespace()
71 | {
72 | $name = 'SimpleMessage';
73 | $type = Entity::TYPE_MESSAGE;
74 | $descriptor = new DescriptorProto();
75 | $fileDescriptor1 = new FileDescriptorProto();
76 | $fileDescriptor2 = new FileDescriptorProto();
77 | $fileDescriptor3 = new FileDescriptorProto();
78 |
79 | $fileDescriptor2->setPackage('package');
80 | $fileDescriptor3->setPackage('package');
81 |
82 | $entity1 = new Entity($type, $name, $descriptor, $fileDescriptor1);
83 | $entity2 = new Entity($type, $name, $descriptor, $fileDescriptor2);
84 | $entity3 = new Entity($type, $name, $descriptor, $fileDescriptor3, 'Parent');
85 |
86 | $this->assertNull($entity1->getNamespace());
87 | $this->assertEquals('package', $entity2->getNamespace());
88 | $this->assertEquals('package\\Parent', $entity3->getNamespace());
89 | }
90 |
91 | public function testGetNamespaceUsingPhpOptions()
92 | {
93 | $name = 'SimpleMessage';
94 | $type = Entity::TYPE_MESSAGE;
95 | $descriptor = new DescriptorProto();
96 | $fileDescriptor1 = new FileDescriptorProto();
97 | $fileDescriptor2 = new FileDescriptorProto();
98 | $fileDescriptor3 = new FileDescriptorProto();
99 |
100 | $fileOptions2 = new FileOptions();
101 | $fileOptions3 = new FileOptions();
102 |
103 | $fileOptions2->extensions()->put(Extension::package(), 'Package');
104 | $fileOptions3->extensions()->put(Extension::package(), 'Package');
105 |
106 | $fileDescriptor1->setPackage('package');
107 | $fileDescriptor2->setPackage('package');
108 | $fileDescriptor3->setPackage('package');
109 | $fileDescriptor2->setOptions($fileOptions2);
110 | $fileDescriptor3->setOptions($fileOptions3);
111 |
112 | $entity1 = new Entity($type, $name, $descriptor, $fileDescriptor1);
113 | $entity2 = new Entity($type, $name, $descriptor, $fileDescriptor2);
114 | $entity3 = new Entity($type, $name, $descriptor, $fileDescriptor3, 'Parent');
115 |
116 | $this->assertEquals('package', $entity1->getNamespace());
117 | $this->assertEquals('Package', $entity2->getNamespace());
118 | $this->assertEquals('Package\\Parent', $entity3->getNamespace());
119 | }
120 |
121 | public function testGetNamespacedName()
122 | {
123 | $name = 'SimpleMessage';
124 | $type = Entity::TYPE_MESSAGE;
125 | $descriptor = new DescriptorProto();
126 | $fileDescriptor1 = new FileDescriptorProto();
127 | $fileDescriptor2 = new FileDescriptorProto();
128 | $fileDescriptor3 = new FileDescriptorProto();
129 |
130 | $fileDescriptor2->setPackage('package');
131 | $fileDescriptor3->setPackage('package');
132 |
133 | $entity1 = new Entity($type, $name, $descriptor, $fileDescriptor1);
134 | $entity2 = new Entity($type, $name, $descriptor, $fileDescriptor2);
135 | $entity3 = new Entity($type, $name, $descriptor, $fileDescriptor3, 'Parent');
136 |
137 | $this->assertEquals('\\SimpleMessage', $entity1->getNamespacedName());
138 | $this->assertEquals('\\package\\SimpleMessage', $entity2->getNamespacedName());
139 | $this->assertEquals('\\package\\Parent\\SimpleMessage', $entity3->getNamespacedName());
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/Generator/Message/WriteFieldStatementGenerator.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | class WriteFieldStatementGenerator extends BaseGenerator
20 | {
21 | /**
22 | * @var string
23 | */
24 | protected $targetVar;
25 |
26 | /**
27 | * @param string $variable
28 | */
29 | public function setTargetVar($variable)
30 | {
31 | $this->targetVar = $variable;
32 | }
33 |
34 | /**
35 | * @param \Protobuf\Compiler\Entity $entity
36 | * @param \google\protobuf\FieldDescriptorProto $field
37 | *
38 | * @return string[]
39 | */
40 | public function generateFieldWriteStatement(Entity $entity, FieldDescriptorProto $field)
41 | {
42 | $body = [];
43 | $name = $field->getName();
44 | $type = $field->getType();
45 | $rule = $field->getLabel();
46 | $tag = $field->getNumber();
47 | $options = $field->getOptions();
48 | $variable = $this->targetVar ?: '$this->' . $name;
49 | $isPack = $options ? $options->getPacked() : false;
50 |
51 | $wire = $isPack ? WireFormat::WIRE_LENGTH : WireFormat::getWireType($type->value(), null);
52 | $key = WireFormat::getFieldKey($tag, $wire);
53 |
54 | if ($rule === Label::LABEL_REPEATED() && $isPack) {
55 | $itemValSttm = ($type === Type::TYPE_ENUM())
56 | ? '$val->value()'
57 | : '$val';
58 |
59 | $body[] = '$innerSize = 0;';
60 | $body[] = '$calculator = $sizeContext->getSizeCalculator();';
61 | $body[] = null;
62 | $body[] = 'foreach (' . $variable . ' as $val) {';
63 | $body[] = ' $innerSize += ' . $this->generateValueSizeStatement($type->value(), $itemValSttm) . ';';
64 | $body[] = '}';
65 | $body[] = null;
66 | $body[] = '$writer->writeVarint($stream, ' . $key . ');';
67 | $body[] = '$writer->writeVarint($stream, $innerSize);';
68 | $body[] = null;
69 | $body[] = 'foreach (' . $variable . ' as $val) {';
70 | $body[] = ' ' . $this->generateWriteScalarStatement($type->value(), $itemValSttm) . ';';
71 | $body[] = '}';
72 |
73 | return $body;
74 | }
75 |
76 | if ($type === Type::TYPE_MESSAGE() && $rule === Label::LABEL_REPEATED()) {
77 | $body[] = 'foreach (' . $variable . ' as $val) {';
78 | $body[] = ' $writer->writeVarint($stream, ' . $key . ');';
79 | $body[] = ' $writer->writeVarint($stream, $val->serializedSize($sizeContext));';
80 | $body[] = ' $val->writeTo($context);';
81 | $body[] = '}';
82 |
83 | return $body;
84 | }
85 |
86 | if ($type === Type::TYPE_ENUM() && $rule === LABEL::LABEL_REPEATED()) {
87 | $body[] = 'foreach (' . $variable . ' as $val) {';
88 | $body[] = ' $writer->writeVarint($stream, ' . $key . ');';
89 | $body[] = ' ' . $this->generateWriteScalarStatement($type->value(), '$val->value()') . ';';
90 | $body[] = '}';
91 |
92 | return $body;
93 | }
94 |
95 | if ($rule === Label::LABEL_REPEATED()) {
96 | $body[] = 'foreach (' . $variable . ' as $val) {';
97 | $body[] = ' $writer->writeVarint($stream, ' . $key . ');';
98 | $body[] = ' ' . $this->generateWriteScalarStatement($type->value(), '$val') . ';';
99 | $body[] = '}';
100 |
101 | return $body;
102 | }
103 |
104 | if ($type === Type::TYPE_ENUM()) {
105 | $body[] = sprintf('$writer->writeVarint($stream, %s);', $key);
106 | $body[] = $this->generateWriteScalarStatement($type->value(), $variable . '->value()') . ';';
107 |
108 | return $body;
109 | }
110 |
111 | if ($type !== Type::TYPE_MESSAGE()) {
112 | $body[] = sprintf('$writer->writeVarint($stream, %s);', $key);
113 | $body[] = $this->generateWriteScalarStatement($type->value(), $variable) . ';';
114 |
115 | return $body;
116 | }
117 |
118 | $body[] = '$writer->writeVarint($stream, ' . $key . ');';
119 | $body[] = '$writer->writeVarint($stream, ' . $variable . '->serializedSize($sizeContext));';
120 | $body[] = $variable . '->writeTo($context);';
121 |
122 | return $body;
123 | }
124 |
125 | /**
126 | * write a scalar value.
127 | *
128 | * @param integer $type
129 | * @param string $value
130 | * @param string $stream
131 | *
132 | * @return array
133 | */
134 | public function generateWriteScalarStatement($type, $value, $stream = '$stream')
135 | {
136 | $mapping = [
137 | Type::TYPE_INT32_VALUE => '$writer->writeVarint(%s, %s)',
138 | Type::TYPE_INT64_VALUE => '$writer->writeVarint(%s, %s)',
139 | Type::TYPE_UINT64_VALUE => '$writer->writeVarint(%s, %s)',
140 | Type::TYPE_UINT32_VALUE => '$writer->writeVarint(%s, %s)',
141 | Type::TYPE_DOUBLE_VALUE => '$writer->writeDouble(%s, %s)',
142 | Type::TYPE_FIXED64_VALUE => '$writer->writeFixed64(%s, %s)',
143 | Type::TYPE_SFIXED64_VALUE => '$writer->writeSFixed64(%s, %s)',
144 | Type::TYPE_FLOAT_VALUE => '$writer->writeFloat(%s, %s)',
145 | Type::TYPE_FIXED32_VALUE => '$writer->writeFixed32(%s, %s)',
146 | Type::TYPE_SFIXED32_VALUE => '$writer->writeSFixed32(%s, %s)',
147 | Type::TYPE_ENUM_VALUE => '$writer->writeVarint(%s, %s)',
148 | Type::TYPE_SINT32_VALUE => '$writer->writeZigzag32(%s, %s)',
149 | Type::TYPE_SINT64_VALUE => '$writer->writeZigzag64(%s, %s)',
150 | Type::TYPE_BOOL_VALUE => '$writer->writeBool(%s, %s)',
151 | Type::TYPE_STRING_VALUE => '$writer->writeString(%s, %s)',
152 | Type::TYPE_BYTES_VALUE => '$writer->writeByteStream(%s, %s)',
153 | ];
154 |
155 | if (isset($mapping[$type])) {
156 | return sprintf($mapping[$type], $stream, $value);
157 | }
158 |
159 | throw new \InvalidArgumentException('Unknown field type : ' . $type);
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/Protoc/ProcessBuilder.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | class ProcessBuilder
18 | {
19 | /**
20 | * @var string
21 | */
22 | protected $plugin;
23 |
24 | /**
25 | * @var string
26 | */
27 | protected $protoc;
28 |
29 | /**
30 | * @var string
31 | */
32 | protected $version;
33 |
34 | /**
35 | * @var bool
36 | */
37 | protected $includeDescriptors = false;
38 |
39 | /**
40 | * @var array
41 | */
42 | protected $descriptorPaths = [];
43 |
44 | /**
45 | * @param string $plugin
46 | * @param string $protoc
47 | * @param string $version
48 | */
49 | public function __construct($plugin, $protoc, $version = '2.3.0')
50 | {
51 | $this->plugin = $plugin;
52 | $this->protoc = $protoc;
53 | $this->version = $version;
54 | }
55 |
56 | /**
57 | * @param bool $flag
58 | */
59 | public function setIncludeDescriptors($flag)
60 | {
61 | $this->includeDescriptors = $flag;
62 | }
63 |
64 | /**
65 | * @param array $paths
66 | */
67 | public function setDescriptorPaths($paths)
68 | {
69 | $this->descriptorPaths = $paths;
70 | }
71 |
72 | /**
73 | * Assert min protoc version.
74 | *
75 | * @throws \UnexpectedValueException Indicates a problem with protoc.
76 | */
77 | public function assertVersion()
78 | {
79 | $process = $this->createProtocVersionProcess();
80 |
81 | // Check if protoc is available
82 | $process->mustRun();
83 |
84 | $return = $process->getExitCode();
85 | $result = trim($process->getOutput());
86 |
87 | if (0 !== $return && 1 !== $return) {
88 | throw new UnexpectedValueException("Unable to find the protoc command. Please make sure it's installed and available in the path.");
89 | }
90 |
91 | if ( ! preg_match('/[0-9\.]+/', $result, $match)) {
92 | throw new UnexpectedValueException("Unable to get protoc command version. Please make sure it's installed and available in the path.");
93 | }
94 |
95 | if (version_compare($match[0], $this->version) < 0) {
96 | throw new UnexpectedValueException("The protoc command in your system is too old. Minimum version required is '{$this->version}' but found '$result'.");
97 | }
98 | }
99 |
100 | /**
101 | * Create a process
102 | *
103 | * @param string|SplFileInfo $outPath
104 | * @param string[]|SplFileInfo[] $protosFiles
105 | * @param string[]|SplFileInfo[] $includeDirs
106 | * @param string[]|SplFileInfo[] $parameters
107 | *
108 | * @return \Symfony\Component\Process\Process
109 | *
110 | * @throws \InvalidArgumentException
111 | */
112 | public function createProcess($outPath, array $protosFiles, array $includeDirs, array $parameters)
113 | {
114 | if (empty($protosFiles)) {
115 | throw new InvalidArgumentException('Proto file list cannot be empty.');
116 | }
117 |
118 | $builder = new SymfonyProcessBuilder();
119 | $outDir = $this->getRealPath($outPath);
120 | $include = $this->getRealPaths($includeDirs);
121 | $protos = $this->getRealPaths($protosFiles, true);
122 |
123 | $builder->setPrefix($this->protoc);
124 |
125 | $builder->add(sprintf('--plugin=protoc-gen-php=%s', $this->plugin));
126 |
127 | foreach ($include as $i) {
128 | $builder->add(sprintf('--proto_path=%s', $i));
129 | }
130 |
131 | if ($this->includeDescriptors) {
132 | $builder->add(sprintf('--proto_path=%s', $this->findDescriptorsPath()));
133 | }
134 |
135 | // Protoc will pass custom arguments to the plugin if they are given
136 | // before a colon character. ie: --php_out="foo=bar:/path/to/plugin"
137 | $out = ( ! empty($parameters))
138 | ? http_build_query($parameters, '', '&') . ':' . $outDir
139 | : $outDir;
140 |
141 | $builder->add(sprintf('--php_out=%s', $out));
142 |
143 | // Add the chosen proto files to generate
144 | foreach ($protos as $proto) {
145 | $builder->add($proto);
146 | }
147 |
148 | return $builder->getProcess();
149 | }
150 |
151 | /**
152 | * @param array $files
153 | * @param bool $isFile
154 | *
155 | * @return array
156 | *
157 | * @throws \InvalidArgumentException
158 | */
159 | protected function getRealPaths(array $files, $isFile = false)
160 | {
161 | $paths = [];
162 |
163 | foreach ($files as $file) {
164 | $paths[] = $this->getRealPath($file, $isFile);
165 | }
166 |
167 | return $paths;
168 | }
169 |
170 | /**
171 | * @param string|SplFileInfo $file
172 | * @param bool $isFile
173 | *
174 | * @return array
175 | *
176 | * @throws \InvalidArgumentException
177 | */
178 | protected function getRealPath($file, $isFile = false)
179 | {
180 | if ( ! $file instanceof SplFileInfo) {
181 | $file = new SplFileInfo($file);
182 | }
183 |
184 | $realpath = $file->getRealPath();
185 |
186 | if (false === $realpath) {
187 | throw new InvalidArgumentException(sprintf(
188 | 'The %s "%s" does not exist.',
189 | ($isFile ? 'file' : 'directory'),
190 | $file->getPathname()
191 | ));
192 | }
193 |
194 | return $realpath;
195 | }
196 |
197 | /**
198 | * @return string
199 | *
200 | * @throws \RuntimeException
201 | */
202 | protected function findDescriptorsPath()
203 | {
204 | foreach ($this->descriptorPaths as $path) {
205 | if ( ! is_dir($path)) {
206 | continue;
207 | }
208 |
209 | return realpath($path) ?: $path;
210 | }
211 |
212 | throw new RuntimeException('Unable to find "protobuf-php/google-protobuf-proto".');
213 | }
214 |
215 | /**
216 | * @return \Symfony\Component\Process\Process
217 | */
218 | public function createProtocVersionProcess()
219 | {
220 | $protoc = $this->protoc;
221 | $process = new Process("$protoc --version");
222 |
223 | return $process;
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/src/Generator/Message/FromArrayGenerator.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | class FromArrayGenerator extends BaseGenerator implements GeneratorVisitor
22 | {
23 | /**
24 | * {@inheritdoc}
25 | */
26 | public function visit(Entity $entity, GeneratorInterface $class)
27 | {
28 | $class->addMethodFromGenerator($this->generateMethod($entity));
29 | }
30 |
31 | /**
32 | * @param \Protobuf\Compiler\Entity $entity
33 | *
34 | * @return string
35 | */
36 | protected function generateMethod(Entity $entity)
37 | {
38 | $lines = $this->generateBody($entity);
39 | $body = implode(PHP_EOL, $lines);
40 | $method = MethodGenerator::fromArray([
41 | 'name' => 'fromArray',
42 | 'body' => $body,
43 | 'static' => true,
44 | 'parameters' => [
45 | [
46 | 'name' => 'values',
47 | 'type' => 'array',
48 | ]
49 | ],
50 | 'docblock' => [
51 | 'shortDescription' => "{@inheritdoc}"
52 | ]
53 | ]);
54 |
55 | return $method;
56 | }
57 |
58 | /**
59 | * @param \Protobuf\Compiler\Entity $entity
60 | *
61 | * @return string[]
62 | */
63 | public function generateBody(Entity $entity)
64 | {
65 | $lines = [];
66 | $addLine = false;
67 | $descriptor = $entity->getDescriptor();
68 | $fields = $descriptor->getFieldList() ?: [];
69 | $defaults = $this->generateDefaultValues($entity);
70 |
71 | foreach ($fields as $field) {
72 | if ($field->getLabel() !== Label::LABEL_REQUIRED()) {
73 | continue;
74 | }
75 |
76 | if ($field->hasDefaultValue()) {
77 | continue;
78 | }
79 |
80 | $item = $this->generateRequiredFieldException($entity, $field);
81 | $lines = array_merge($lines, $item, [null]);
82 | }
83 |
84 | $lines[] = '$message = new self();';
85 | $lines[] = '$values = array_merge([';
86 | $lines = array_merge($lines, $this->addIndentation($defaults, 1));
87 | $lines[] = '], $values);';
88 | $lines[] = null;
89 |
90 | foreach ($fields as $field) {
91 | if ($field->getLabel() === Label::LABEL_REPEATED()) {
92 | continue;
93 | }
94 |
95 | $item = $this->generateSetValue($entity, $field);
96 | $lines = array_merge($lines, $item);
97 |
98 | $addLine = true;
99 | }
100 |
101 | if ($addLine) {
102 | $lines[] = null;
103 | }
104 |
105 | foreach ($fields as $field) {
106 | if ($field->getLabel() !== Label::LABEL_REPEATED()) {
107 | continue;
108 | }
109 |
110 | $item = $this->generateAddValue($entity, $field);
111 | $lines = array_merge($lines, $item, [null]);
112 | }
113 |
114 | $lines[] = 'return $message;';
115 |
116 | return $lines;
117 | }
118 |
119 | /**
120 | * @param \Protobuf\Compiler\Entity $entity
121 | * @param \google\protobuf\FieldDescriptorProto $field
122 | *
123 | * @return string[]
124 | */
125 | protected function generateRequiredFieldException(Entity $entity, FieldDescriptorProto $field)
126 | {
127 | $name = $field->getName();
128 | $tag = $field->getNumber();
129 |
130 | $class = $entity->getNamespacedName();
131 | $format = 'Field "%s" (tag %s) is required but has no value.';
132 | $message = var_export(sprintf($format, $name, $tag), true);
133 |
134 | $body[] = 'if ( ! isset($values[\'' . $name . '\'])) {';
135 | $body[] = ' throw new \InvalidArgumentException(' . $message . ');';
136 | $body[] = '}';
137 |
138 | return $body;
139 | }
140 |
141 | /**
142 | * @param \Protobuf\Compiler\Entity $entity
143 | * @param \google\protobuf\FieldDescriptorProto $field
144 | *
145 | * @return array
146 | */
147 | protected function generateSetValue(Entity $entity, FieldDescriptorProto $field)
148 | {
149 | $lines = [];
150 | $fieldName = $field->getName();
151 | $methodName = $this->getAccessorName('set', $field);
152 |
153 | $lines[] = '$message->' . $methodName . '($values[\'' . $fieldName . '\']);';
154 |
155 | return $lines;
156 | }
157 |
158 | /**
159 | * @param \Protobuf\Compiler\Entity $entity
160 | * @param \google\protobuf\FieldDescriptorProto $field
161 | *
162 | * @return array
163 | */
164 | protected function generateAddValue(Entity $entity, FieldDescriptorProto $field)
165 | {
166 | $lines = [];
167 | $fieldName = $field->getName();
168 | $methodName = 'add' . $this->getClassifiedName($field);
169 |
170 | $lines[] = 'foreach ($values[\'' . $fieldName . '\'] as $item) {';
171 | $lines[] = ' $message->' . $methodName . '($item);';
172 | $lines[] = '}';
173 |
174 | return $lines;
175 | }
176 |
177 | /**
178 | * @param \Protobuf\Compiler\Entity $entity
179 | *
180 | * @return array
181 | */
182 | protected function generateDefaultValues(Entity $entity)
183 | {
184 | $descriptor = $entity->getDescriptor();
185 | $fields = $descriptor->getFieldList() ?: [];
186 | $size = count($fields);
187 | $lines = [];
188 |
189 | foreach ($fields as $i => $field) {
190 | $name = $field->getName();
191 | $comma = ($i +1 < $size) ? ',' : '';
192 | $value = $this->getDefaultFieldValue($field);
193 |
194 | // required field throw InvalidArgumentException
195 | if ($field->getLabel() === Label::LABEL_REQUIRED()) {
196 | continue;
197 | }
198 |
199 | if ($field->getLabel() === Label::LABEL_REPEATED()) {
200 | $value = '[]';
201 | }
202 |
203 | $lines[] = "'$name' => $value" . $comma;
204 | }
205 |
206 | return $lines;
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/src/Generator/EnumGenerator.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | class EnumGenerator extends BaseGenerator implements EntityVisitor
20 | {
21 | /**
22 | * {@inheritdoc}
23 | */
24 | public function visit(Entity $entity)
25 | {
26 | $name = $entity->getName();
27 | $namespace = $entity->getNamespace();
28 | $shortDescription = 'Protobuf enum : ' . $entity->getClass();
29 | $class = ClassGenerator::fromArray([
30 | 'name' => $name,
31 | 'namespacename' => $namespace,
32 | 'extendedclass' => '\Protobuf\Enum',
33 | 'methods' => $this->generateMethods($entity),
34 | 'properties' => $this->generateProperties($entity),
35 | 'docblock' => [
36 | 'shortDescription' => $shortDescription,
37 | ]
38 | ]);
39 |
40 | $entity->setContent($this->generateFileContent($class, $entity));
41 | }
42 |
43 | /**
44 | * @param \Protobuf\Compiler\Entity $entity
45 | *
46 | * @return string[]
47 | */
48 | protected function generateProperties(Entity $entity)
49 | {
50 | $descriptor = $entity->getDescriptor();
51 | $class = $entity->getNamespacedName();
52 | $properties = $this->generateConstants($entity);
53 | $values = $descriptor->getValueList() ?: [];
54 |
55 | foreach ($values as $value) {
56 | $properties[] = PropertyGenerator::fromArray([
57 | 'static' => true,
58 | 'name' => $value->getName(),
59 | 'visibility' => PropertyGenerator::VISIBILITY_PROTECTED,
60 | 'docblock' => [
61 | 'tags' => [
62 | [
63 | 'name' => 'var',
64 | 'description' => $class,
65 | ]
66 | ]
67 | ]
68 | ]);
69 | }
70 |
71 | foreach ($properties as $value) {
72 | $value->getDocblock()->setWordWrap(false);
73 | }
74 |
75 | return $properties;
76 | }
77 |
78 | /**
79 | * @param \Protobuf\Compiler\Entity $entity
80 | *
81 | * @return string[]
82 | */
83 | public function generateConstants(Entity $entity)
84 | {
85 | $constants = [];
86 | $descriptor = $entity->getDescriptor();
87 | $values = $descriptor->getValueList() ?: [];
88 |
89 | foreach ($values as $value) {
90 | $name = $value->getName();
91 | $number = $value->getNumber();
92 | $constant = PropertyGenerator::fromArray([
93 | 'const' => true,
94 | 'defaultvalue' => $number,
95 | 'name' => $name . '_VALUE',
96 | 'docblock' => [
97 | 'shortDescription' => "$name = $number",
98 | ]
99 | ]);
100 |
101 | $constants[] = $constant;
102 | }
103 |
104 | return $constants;
105 | }
106 |
107 | /**
108 | * @param \Protobuf\Compiler\Entity $entity
109 | *
110 | * @return string[]
111 | */
112 | public function generateMethods(Entity $entity)
113 | {
114 | $methods = [];
115 | $descriptor = $entity->getDescriptor();
116 | $values = $descriptor->getValueList() ?: [];
117 |
118 | foreach ($values as $value) {
119 | $methods[] = $this->generateMethod($entity, $value);
120 | }
121 |
122 | $methods[] = $this->generateValueOfMethod($entity);
123 |
124 | return $methods;
125 | }
126 |
127 | /**
128 | * @param \Protobuf\Compiler\Entity $entity
129 | * @param EnumValueDescriptorProto $value
130 | *
131 | * @return string
132 | */
133 | public function generateMethod(Entity $entity, EnumValueDescriptorProto $value)
134 | {
135 | $body = [];
136 | $name = $value->getName();
137 | $number = $value->getNumber();
138 | $class = $entity->getNamespacedName();
139 | $args = var_export($name, true) . ', self::' . $name . '_VALUE';
140 |
141 | $body[] = 'if (self::$' . $name . ' !== null) {';
142 | $body[] = ' return self::$' . $name . ';';
143 | $body[] = '}';
144 | $body[] = null;
145 | $body[] = 'return self::$' . $name . ' = new self(' . $args . ');';
146 |
147 | $method = MethodGenerator::fromArray([
148 | 'static' => true,
149 | 'name' => $name,
150 | 'body' => implode(PHP_EOL, $body),
151 | 'docblock' => [
152 | 'tags' => [
153 | [
154 | 'name' => 'return',
155 | 'description' => $class,
156 | ]
157 | ]
158 | ]
159 | ]);
160 |
161 | $method->getDocblock()->setWordWrap(false);
162 |
163 | return $method;
164 | }
165 |
166 | /**
167 | * @param \Protobuf\Compiler\Entity $entity
168 | *
169 | * @return string
170 | */
171 | public function generateValueOfMethod(Entity $entity)
172 | {
173 | $body = [];
174 | $descriptor = $entity->getDescriptor();
175 | $class = $entity->getNamespacedName();
176 | $values = $descriptor->getValueList() ?: [];
177 |
178 | $body[] = 'switch ($value) {';
179 |
180 | foreach ($values as $value) {
181 | $name = $value->getName();
182 | $number = $value->getNumber();
183 |
184 | $body[] = ' case ' . $number . ': return self::' . $name . '();';
185 | }
186 |
187 | $body[] = ' default: return null;';
188 | $body[] = '}';
189 |
190 | $method = MethodGenerator::fromArray([
191 | 'static' => true,
192 | 'name' => 'valueOf',
193 | 'body' => implode(PHP_EOL, $body),
194 | 'parameters' => [
195 | [
196 | 'name' => 'value',
197 | 'type' => 'int',
198 | ]
199 | ],
200 | 'docblock' => [
201 | 'tags' => [
202 | [
203 | 'name' => 'param',
204 | 'description' => 'int $value',
205 | ],
206 | [
207 | 'name' => 'return',
208 | 'description' => $class,
209 | ]
210 | ]
211 | ]
212 | ]);
213 |
214 | $method->getDocblock()->setWordWrap(false);
215 |
216 | return $method;
217 | }
218 | }
219 |
--------------------------------------------------------------------------------
/tests/Generator/Message/SerializedSizeFieldStatementGeneratorTest.php:
--------------------------------------------------------------------------------
1 | createContext([
17 | [
18 | 'name' => 'simple.proto',
19 | 'package' => 'ProtobufCompilerTest.Protos',
20 | 'values' => [
21 | 'messages' => [
22 | [
23 | 'name' => 'Simple',
24 | 'fields' => $fields
25 | ]
26 | ]
27 | ]
28 | ]
29 | ]);
30 | }
31 |
32 | public function descriptorProvider()
33 | {
34 | return [
35 |
36 | // required int32
37 | [
38 | [
39 | 1 => ['count', Field::TYPE_INT32, Field::LABEL_REQUIRED]
40 | ],
41 | <<<'CODE'
42 | $size += 1;
43 | $size += $calculator->computeVarintSize($this->count);
44 | CODE
45 | ],
46 |
47 | // repeated string
48 | [
49 | [
50 | 1 => ['lines', Field::TYPE_STRING, Field::LABEL_REPEATED]
51 | ],
52 | <<<'CODE'
53 | foreach ($this->lines as $val) {
54 | $size += 1;
55 | $size += $calculator->computeStringSize($val);
56 | }
57 | CODE
58 | ],
59 |
60 | // required int32 packed
61 | [
62 | [
63 | 1 => ['tags', Field::TYPE_INT32, Field::LABEL_REPEATED, null, [ 'options' => ['packed' => true] ]]
64 | ],
65 | <<<'CODE'
66 | $innerSize = 0;
67 |
68 | foreach ($this->tags as $val) {
69 | $innerSize += $calculator->computeVarintSize($val);
70 | }
71 |
72 | $size += 1;
73 | $size += $innerSize;
74 | $size += $calculator->computeVarintSize($innerSize);
75 | CODE
76 | ],
77 |
78 | // required message
79 | [
80 | [
81 | 1 => ['phone', Field::TYPE_MESSAGE, Field::LABEL_REQUIRED, 'ProtobufCompiler.Proto.PhoneNumber']
82 | ],
83 | <<<'CODE'
84 | $innerSize = $this->phone->serializedSize($context);
85 |
86 | $size += 1;
87 | $size += $innerSize;
88 | $size += $calculator->computeVarintSize($innerSize);
89 | CODE
90 | ],
91 |
92 | // repeated message
93 | [
94 | [
95 | 1 => ['files', Field::TYPE_MESSAGE, Field::LABEL_REPEATED, 'ProtobufCompiler.Proto.File']
96 | ],
97 | <<<'CODE'
98 | foreach ($this->files as $val) {
99 | $innerSize = $val->serializedSize($context);
100 |
101 | $size += 1;
102 | $size += $innerSize;
103 | $size += $calculator->computeVarintSize($innerSize);
104 | }
105 | CODE
106 | ],
107 | ];
108 | }
109 |
110 | /**
111 | * @dataProvider descriptorProvider
112 | */
113 | public function testGenerateFieldSizeStatement($fields, $expected, $fieldIndex = 0)
114 | {
115 | $context = $this->createMessagesContext($fields);
116 | $entity = $context->getEntity('ProtobufCompilerTest.Protos.Simple');
117 | $generator = new SerializedSizeFieldStatementGenerator($context);
118 | $descritor = $entity->getDescriptor();
119 | $field = $descritor->getFieldList()[$fieldIndex];
120 | $actual = $generator->generateFieldSizeStatement($entity, $field);
121 |
122 | $this->assertEquals($expected, implode(PHP_EOL, $actual));
123 | }
124 |
125 | public function testGenerateEnumRepeatedFieldSizeStatement()
126 | {
127 | $context = $this->createContext([
128 | [
129 | 'name' => 'simple.proto',
130 | 'package' => 'ProtobufCompilerTest.Protos',
131 | 'values' => [
132 | 'messages' => [
133 | [
134 | 'name' => 'Simple',
135 | 'fields' => [
136 | 1 => ['status', Field::TYPE_ENUM, Field::LABEL_REPEATED, 'ProtobufCompilerTest.Protos.Type']
137 | ]
138 | ],
139 | [
140 | 'name' => 'Type',
141 | 'fields' => []
142 | ]
143 | ]
144 | ]
145 | ]
146 | ]);
147 |
148 | $entity = $context->getEntity('ProtobufCompilerTest.Protos.Simple');
149 | $generator = new SerializedSizeFieldStatementGenerator($context);
150 | $descritor = $entity->getDescriptor();
151 | $field = $descritor->getFieldList()[0];
152 |
153 | $actual = $generator->generateFieldSizeStatement($entity, $field);
154 | $expected = <<<'CODE'
155 | foreach ($this->status as $val) {
156 | $size += 1;
157 | $size += $calculator->computeVarintSize($val->value());
158 | }
159 | CODE;
160 |
161 | $this->assertEquals($expected, implode(PHP_EOL, $actual));
162 | }
163 |
164 | public function testGenerateEnumPackadRepeatedFieldSizeStatement()
165 | {
166 | $options = new FieldOptions();
167 | $context = $this->createContext([
168 | [
169 | 'name' => 'simple.proto',
170 | 'package' => 'ProtobufCompilerTest.Protos',
171 | 'values' => [
172 | 'messages' => [
173 | [
174 | 'name' => 'Simple',
175 | 'fields' => [
176 | 1 => ['status', Field::TYPE_ENUM, Field::LABEL_REPEATED, 'ProtobufCompilerTest.Protos.Type']
177 | ]
178 | ],
179 | [
180 | 'name' => 'Type',
181 | 'fields' => []
182 | ]
183 | ]
184 | ]
185 | ]
186 | ]);
187 |
188 | $entity = $context->getEntity('ProtobufCompilerTest.Protos.Simple');
189 | $generator = new SerializedSizeFieldStatementGenerator($context);
190 | $descritor = $entity->getDescriptor();
191 | $field = $descritor->getFieldList()[0];
192 |
193 | $options->setPacked(true);
194 | $field->setOptions($options);
195 |
196 | $actual = $generator->generateFieldSizeStatement($entity, $field);
197 | $expected = <<<'CODE'
198 | $innerSize = 0;
199 |
200 | foreach ($this->status as $val) {
201 | $innerSize += $calculator->computeVarintSize($val->value());
202 | }
203 |
204 | $size += 1;
205 | $size += $innerSize;
206 | $size += $calculator->computeVarintSize($innerSize);
207 | CODE;
208 |
209 | $this->assertEquals($expected, implode(PHP_EOL, $actual));
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/tests/EntityBuilderTest.php:
--------------------------------------------------------------------------------
1 | createFileDescriptorProto('simple.proto', 'Protos', [
21 | 'messages' => [
22 | [
23 | 'name' => 'Simple',
24 | 'fields' => []
25 | ]
26 | ]
27 | ]);
28 |
29 | $descriptor2 = $this->createFileDescriptorProto('include.proto', 'Protos', [
30 | 'messages' => [
31 | [
32 | 'name' => 'Include',
33 | 'fields' => []
34 | ]
35 | ]
36 | ]);
37 |
38 | $request = new CodeGeneratorRequest();
39 |
40 | $request->addProtoFile($descriptor1);
41 | $request->addProtoFile($descriptor2);
42 | $request->addFileToGenerate($descriptor1->getName());
43 |
44 | $builder = new EntityBuilder($request);
45 | $entities = $builder->buildEntities();
46 |
47 | $this->assertCount(2, $entities);
48 | $this->assertArrayhasKey('Protos.Simple', $entities);
49 | $this->assertArrayhasKey('Protos.Include', $entities);
50 |
51 | $this->assertInstanceOf(Entity::CLASS, $entities['Protos.Simple']);
52 | $this->assertInstanceOf(Entity::CLASS, $entities['Protos.Include']);
53 |
54 | $this->assertEquals('Protos.Simple', $entities['Protos.Simple']->getClass());
55 | $this->assertEquals('Protos.Include', $entities['Protos.Include']->getClass());
56 |
57 | $this->assertEquals(Entity::TYPE_MESSAGE, $entities['Protos.Simple']->getType());
58 | $this->assertEquals(Entity::TYPE_MESSAGE, $entities['Protos.Include']->getType());
59 |
60 | $this->assertTrue($entities['Protos.Simple']->isFileToGenerate());
61 | $this->assertFalse($entities['Protos.Include']->isFileToGenerate());
62 | }
63 |
64 | public function testBuildFileEntities()
65 | {
66 | $request = new CodeGeneratorRequest();
67 | $descriptor = $this->createFileDescriptorProto('simple.proto', 'Protos', [
68 | 'messages' => [
69 | [
70 | 'name' => 'ParentMessage',
71 | 'fields' => [],
72 | 'values' => [
73 | 'messages' => [
74 | [
75 | 'name' => 'InnerMessage',
76 | 'fields' => [],
77 | 'values' => [
78 | 'enums' => [
79 | [
80 | 'name' => 'InnerMessageEnum',
81 | 'values' => []
82 | ]
83 | ]
84 | ]
85 | ]
86 | ]
87 | ]
88 | ]
89 | ],
90 | 'extensions' => [
91 | 200 => ['extension', Field::TYPE_STRING, Field::LABEL_OPTIONAL, '.google.protobuf.MessageOptions']
92 | ],
93 | 'enums' => [
94 | [
95 | 'name' => 'EnumType',
96 | 'values' => []
97 | ]
98 | ],
99 | 'services' => [
100 | [
101 | 'name' => 'SearchService',
102 | 'values' => []
103 | ]
104 | ]
105 | ]);
106 |
107 | $builder = new EntityBuilder($request);
108 | $entities = $this->invokeMethod($builder, 'buildFileEntities', [$descriptor]);
109 |
110 | $this->assertCount(6, $entities);
111 | $this->assertInstanceOf(Entity::CLASS, $entities[0]);
112 | $this->assertInstanceOf(Entity::CLASS, $entities[1]);
113 | $this->assertInstanceOf(Entity::CLASS, $entities[2]);
114 | $this->assertInstanceOf(Entity::CLASS, $entities[3]);
115 | $this->assertInstanceOf(Entity::CLASS, $entities[4]);
116 | $this->assertInstanceOf(Entity::CLASS, $entities[5]);
117 |
118 | $this->assertEquals('Protos.ParentMessage', $entities[0]->getClass());
119 | $this->assertEquals('Protos.ParentMessage.InnerMessage', $entities[1]->getClass());
120 | $this->assertEquals('Protos.ParentMessage.InnerMessage.InnerMessageEnum', $entities[2]->getClass());
121 | $this->assertEquals('Protos.SearchService', $entities[3]->getClass());
122 | $this->assertEquals('Protos.EnumType', $entities[4]->getClass());
123 | $this->assertEquals('Protos.Extension', $entities[5]->getClass());
124 | $this->assertTrue($entities[5]->isProtobufExtension());
125 |
126 | $this->assertEquals(Entity::TYPE_MESSAGE, $entities[0]->getType());
127 | $this->assertEquals(Entity::TYPE_MESSAGE, $entities[1]->getType());
128 | $this->assertEquals(Entity::TYPE_ENUM, $entities[2]->getType());
129 | $this->assertEquals(Entity::TYPE_SERVICE, $entities[3]->getType());
130 | $this->assertEquals(Entity::TYPE_ENUM, $entities[4]->getType());
131 | $this->assertEquals(Entity::TYPE_EXTENSION, $entities[5]->getType());
132 | }
133 |
134 | public function testHasExtension()
135 | {
136 | $descriptor1 = $this->createFileDescriptorProto('simple.proto', 'Protos', [
137 | 'extensions' => [
138 | 200 => ['extension', Field::TYPE_STRING, Field::LABEL_OPTIONAL, '.Protos.Extendee']
139 | ]
140 | ]);
141 |
142 | $descriptor2 = $this->createFileDescriptorProto('simple.proto', 'Protos', [
143 | 'messages' => [
144 | [
145 | 'name' => 'Dog',
146 | 'fields' => [],
147 | 'values' => [
148 | 'extensions' => [
149 | 101 => ['animal', Field::TYPE_MESSAGE, Field::LABEL_OPTIONAL, '.ProtobufCompilerTest.Protos.Extension.Animal', '.ProtobufCompilerTest.Protos.Extension.Dog']
150 | ]
151 | ]
152 | ]
153 | ]
154 | ]);
155 |
156 | $descriptor3 = $this->createFileDescriptorProto('simple.proto', 'Protos', []);
157 | $descriptor4 = $this->createFileDescriptorProto('simple.proto', 'Protos', [
158 | 'messages' => [
159 | [
160 | 'name' => 'Simple',
161 | 'fields' => []
162 | ]
163 | ]
164 | ]);
165 |
166 | $request = new CodeGeneratorRequest();
167 | $builder = new EntityBuilder($request);
168 |
169 | $this->assertTrue($this->invokeMethod($builder, 'hasExtension', [$descriptor1]));
170 | $this->assertTrue($this->invokeMethod($builder, 'hasExtension', [$descriptor2]));
171 | $this->assertFalse($this->invokeMethod($builder, 'hasExtension', [$descriptor3]));
172 | $this->assertFalse($this->invokeMethod($builder, 'hasExtension', [$descriptor4]));
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/src/Generator/ExtensionGenerator.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | class ExtensionGenerator extends BaseGenerator implements EntityVisitor
26 | {
27 | /**
28 | * {@inheritdoc}
29 | */
30 | public function visit(Entity $entity)
31 | {
32 | $name = $entity->getName();
33 | $namespace = $entity->getNamespace();
34 | $shortDescription = 'Protobuf extension : ' . $entity->getClass();
35 | $class = ClassGenerator::fromArray([
36 | 'name' => $name,
37 | 'namespacename' => $namespace,
38 | 'implementedinterfaces' => ['\Protobuf\Extension'],
39 | 'properties' => $this->generateFields($entity),
40 | 'methods' => $this->generateMethods($entity),
41 | 'docblock' => [
42 | 'shortDescription' => $shortDescription
43 | ]
44 | ]);
45 |
46 | $entity->setContent($this->generateFileContent($class, $entity));
47 | }
48 |
49 | /**
50 | * @param \Protobuf\Compiler\Entity $entity
51 | *
52 | * @return string[]
53 | */
54 | protected function generateFields(Entity $entity)
55 | {
56 | $fields = [];
57 | $descriptor = $entity->getDescriptor();
58 | $extensions = $descriptor->getExtensionList() ?: [];
59 |
60 | foreach ($extensions as $field) {
61 | $fields[] = $this->generateExtensionField($entity, $field);
62 | }
63 |
64 | return $fields;
65 | }
66 |
67 | /**
68 | * @param \Protobuf\Compiler\Entity $entity
69 | * @param \google\protobuf\FieldDescriptorProto $field
70 | *
71 | * @return string
72 | */
73 | protected function generateExtensionField(Entity $entity, FieldDescriptorProto $field)
74 | {
75 | $name = $field->getName();
76 | $number = $field->getNumber();
77 | $docBlock = $this->getDocBlockType($field);
78 | $type = $this->getFieldTypeName($field);
79 | $label = $this->getFieldLabelName($field);
80 | $property = PropertyGenerator::fromArray([
81 | 'static' => true,
82 | 'name' => $name,
83 | 'visibility' => PropertyGenerator::VISIBILITY_PROTECTED,
84 | 'docblock' => [
85 | 'shortDescription' => "Extension field : $name $label $type = $number",
86 | 'tags' => [
87 | [
88 | 'name' => 'var',
89 | 'description' => '\Protobuf\Extension',
90 | ]
91 | ]
92 | ]
93 | ]);
94 |
95 | $property->getDocblock()->setWordWrap(false);
96 |
97 | return $property;
98 | }
99 |
100 | /**
101 | * @param \Protobuf\Compiler\Entity $entity
102 | *
103 | * @return string[]
104 | */
105 | protected function generateMethods(Entity $entity)
106 | {
107 | $descriptor = $entity->getDescriptor();
108 | $extensions = $descriptor->getExtensionList() ?: [];
109 | $methods = [$this->generateRegisterAllExtensionsMethod($entity)];
110 |
111 | foreach ($extensions as $field) {
112 | $methods[] = $this->generateExtensionMethod($entity, $field);
113 | }
114 |
115 | return $methods;
116 | }
117 |
118 | /**
119 | * @param \Protobuf\Compiler\Entity $entity
120 | * @param \google\protobuf\FieldDescriptorProto $field
121 | *
122 | * @return string
123 | */
124 | protected function generateExtensionMethod(Entity $entity, FieldDescriptorProto $field)
125 | {
126 | $fieldName = $field->getName();
127 | $descriptor = $entity->getDescriptor();
128 | $methodName = $this->getCamelizedName($field);
129 | $bodyGen = new ExtensionsGenerator($this->context);
130 | $body = implode(PHP_EOL, $bodyGen->generateBody($entity, $field));
131 | $method = MethodGenerator::fromArray([
132 | 'static' => true,
133 | 'body' => $body,
134 | 'name' => $methodName,
135 | 'docblock' => [
136 | 'shortDescription' => "Extension field : $fieldName",
137 | 'tags' => [
138 | [
139 | 'name' => 'return',
140 | 'description' => '\Protobuf\Extension',
141 | ]
142 | ]
143 | ]
144 | ]);
145 |
146 | $method->getDocblock()->setWordWrap(false);
147 |
148 | return $method;
149 | }
150 |
151 | /**
152 | * @param \Protobuf\Compiler\Entity $entity
153 | *
154 | * @return string
155 | */
156 | protected function generateRegisterAllExtensionsMethod(Entity $entity)
157 | {
158 | $lines = [];
159 | $fields = [];
160 | $descriptor = $entity->getDescriptor();
161 | $extensions = $descriptor->getExtensionList() ?: [];
162 | $messages = $descriptor->getMessageTypeList() ?: [];
163 |
164 | foreach ($messages as $message) {
165 |
166 | if ( ! $message->hasExtensionList()) {
167 | continue;
168 | }
169 |
170 | foreach ($message->getExtensionList() as $extension) {
171 | $fields[] = $extension;
172 | }
173 | }
174 |
175 | foreach ($fields as $field) {
176 | $type = $field->getTypeName();
177 | $name = $this->getCamelizedName($field);
178 |
179 | $ref = $this->getEntity($type);
180 | $class = $ref->getNamespacedName();
181 | $lines[] = '$registry->add(' . $class . '::' . $name. '());';
182 | }
183 |
184 | foreach ($extensions as $field) {
185 | $type = $field->getTypeName();
186 | $name = $this->getCamelizedName($field);
187 |
188 | $lines[] = '$registry->add(self::' . $name . '());';
189 | }
190 |
191 | $body = implode(PHP_EOL, $lines);
192 | $method = MethodGenerator::fromArray([
193 | 'static' => true,
194 | 'body' => $body,
195 | 'name' => 'registerAllExtensions',
196 | 'parameters' => [
197 | [
198 | 'name' => 'registry',
199 | 'type' => '\Protobuf\Extension\ExtensionRegistry'
200 | ]
201 | ],
202 | 'docblock' => [
203 | 'shortDescription' => "Register all extensions",
204 | 'tags' => [
205 | [
206 | 'name' => 'param',
207 | 'description' => '\Protobuf\Extension\ExtensionRegistry',
208 | ]
209 | ]
210 | ]
211 | ]);
212 |
213 | return $method;
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/src/Generator/Message/ReadFieldStatementGenerator.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | class ReadFieldStatementGenerator extends BaseGenerator
22 | {
23 | const BREAK_MODE_CONTINUE = 1;
24 | const BREAK_MODE_RETURN = 2;
25 |
26 | /**
27 | * @var string
28 | */
29 | protected $breakMode = self::BREAK_MODE_CONTINUE;
30 |
31 | /**
32 | * @var string
33 | */
34 | protected $targetVar;
35 |
36 | /**
37 | * @param integer $mode
38 | */
39 | public function setBreakMode($mode)
40 | {
41 | $this->breakMode = $mode;
42 | }
43 |
44 | /**
45 | * @param string $variable
46 | */
47 | public function setTargetVar($variable)
48 | {
49 | $this->targetVar = $variable;
50 | }
51 |
52 | /**
53 | * @param \Protobuf\Compiler\Entity $entity
54 | * @param \google\protobuf\FieldDescriptorProto $field
55 | *
56 | * @return string[]
57 | */
58 | public function generateFieldReadStatement(Entity $entity, FieldDescriptorProto $field)
59 | {
60 | $body = [];
61 | $reference = null;
62 | $type = $field->getType();
63 | $name = $field->getName();
64 | $rule = $field->getLabel();
65 | $tag = $field->getNumber();
66 | $options = $field->getOptions();
67 | $isPack = $options ? $options->getPacked() : false;
68 | $variable = $this->targetVar ?: '$this->' . $name;
69 | $breakSttm = $this->getBreakStatement($variable);
70 |
71 | if ($field->hasTypeName()) {
72 | $typeName = $field->getTypeName();
73 | $typeEntity = $this->getEntity($typeName);
74 | $reference = $typeEntity->getNamespacedName();
75 | }
76 |
77 | if ( ! $isPack) {
78 | $body[] = sprintf('\Protobuf\WireFormat::assertWireType($wire, %s);', $type->value());
79 | $body[] = null;
80 | }
81 |
82 | if ($rule === Label::LABEL_REPEATED() && $isPack) {
83 | $readSttm = ($type === Type::TYPE_ENUM())
84 | ? $reference . '::valueOf(' . $this->generateReadScalarStatement($type->value()) .')'
85 | : $this->generateReadScalarStatement($type->value());
86 |
87 | $body[] = '$innerSize = $reader->readVarint($stream);';
88 | $body[] = '$innerLimit = $stream->tell() + $innerSize;';
89 | $body[] = null;
90 | $body[] = 'if (' . $variable . ' === null) {';
91 | $body[] = ' ' . $variable . ' = new ' . $this->getCollectionClassName($field) . '();';
92 | $body[] = '}';
93 | $body[] = null;
94 | $body[] = 'while ($stream->tell() < $innerLimit) {';
95 | $body[] = ' ' . $variable . '->add(' . $readSttm . ');';
96 | $body[] = '}';
97 | $body[] = null;
98 | $body[] = $breakSttm;
99 |
100 | return $body;
101 | }
102 |
103 | if ($type === Type::TYPE_MESSAGE() && $rule === Label::LABEL_REPEATED()) {
104 | $body[] = '$innerSize = $reader->readVarint($stream);';
105 | $body[] = '$innerMessage = new ' . $reference . '();';
106 | $body[] = null;
107 | $body[] = 'if (' . $variable . ' === null) {';
108 | $body[] = ' ' . $variable . ' = new \Protobuf\MessageCollection();';
109 | $body[] = '}';
110 | $body[] = null;
111 | $body[] = $variable . '->add($innerMessage);';
112 | $body[] = null;
113 | $body[] = '$context->setLength($innerSize);';
114 | $body[] = '$innerMessage->readFrom($context);';
115 | $body[] = '$context->setLength($length);';
116 | $body[] = null;
117 | $body[] = $breakSttm;
118 |
119 | return $body;
120 | }
121 |
122 | if ($type === Type::TYPE_ENUM() && $rule === LABEL::LABEL_REPEATED()) {
123 | $body[] = 'if (' . $variable . ' === null) {';
124 | $body[] = ' ' . $variable . ' = new ' . $this->getCollectionClassName($field) . '();';
125 | $body[] = '}';
126 | $body[] = null;
127 | $body[] = $variable . '->add(' . $reference . '::valueOf(' . $this->generateReadScalarStatement($type->value()) . '));';
128 | $body[] = null;
129 | $body[] = $breakSttm;
130 |
131 | return $body;
132 | }
133 |
134 | if ($type === Type::TYPE_MESSAGE()) {
135 | $body[] = '$innerSize = $reader->readVarint($stream);';
136 | $body[] = '$innerMessage = new ' . $reference . '();';
137 | $body[] = null;
138 | $body[] = $variable . ' = $innerMessage;';
139 | $body[] = null;
140 | $body[] = '$context->setLength($innerSize);';
141 | $body[] = '$innerMessage->readFrom($context);';
142 | $body[] = '$context->setLength($length);';
143 | $body[] = null;
144 | $body[] = $breakSttm;
145 |
146 | return $body;
147 | }
148 |
149 | if ($type === Type::TYPE_ENUM()) {
150 | $body[] = $variable . ' = ' . $reference . '::valueOf(' . $this->generateReadScalarStatement($type->value()) . ');';
151 | $body[] = null;
152 | $body[] = $breakSttm;
153 |
154 | return $body;
155 | }
156 |
157 | if ($rule !== LABEL::LABEL_REPEATED()) {
158 | $body[] = $variable . ' = ' . $this->generateReadScalarStatement($type->value()) . ';';
159 | $body[] = null;
160 | $body[] = $breakSttm;
161 |
162 | return $body;
163 | }
164 |
165 | $body[] = 'if (' . $variable . ' === null) {';
166 | $body[] = ' ' . $variable . ' = new ' . $this->getCollectionClassName($field) . '();';
167 | $body[] = '}';
168 | $body[] = null;
169 | $body[] = $variable . '->add(' . $this->generateReadScalarStatement($type->value()) . ');';
170 | $body[] = null;
171 | $body[] = $breakSttm;
172 |
173 | return $body;
174 | }
175 |
176 | /**
177 | * read a scalar value.
178 | *
179 | * @param integer $type
180 | *
181 | * @return string
182 | */
183 | protected function generateReadScalarStatement($type)
184 | {
185 | $mapping = [
186 | Type::TYPE_INT32_VALUE => '$reader->readVarint($stream)',
187 | Type::TYPE_INT64_VALUE => '$reader->readVarint($stream)',
188 | Type::TYPE_UINT64_VALUE => '$reader->readVarint($stream)',
189 | Type::TYPE_UINT32_VALUE => '$reader->readVarint($stream)',
190 | Type::TYPE_DOUBLE_VALUE => '$reader->readDouble($stream)',
191 | Type::TYPE_FIXED64_VALUE => '$reader->readFixed64($stream)',
192 | Type::TYPE_SFIXED64_VALUE => '$reader->readSFixed64($stream)',
193 | Type::TYPE_FLOAT_VALUE => '$reader->readFloat($stream)',
194 | Type::TYPE_FIXED32_VALUE => '$reader->readFixed32($stream)',
195 | Type::TYPE_SFIXED32_VALUE => '$reader->readSFixed32($stream)',
196 | Type::TYPE_ENUM_VALUE => '$reader->readVarint($stream)',
197 | Type::TYPE_SINT32_VALUE => '$reader->readZigzag($stream)',
198 | Type::TYPE_SINT64_VALUE => '$reader->readZigzag($stream)',
199 | Type::TYPE_BOOL_VALUE => '$reader->readBool($stream)',
200 | Type::TYPE_STRING_VALUE => '$reader->readString($stream)',
201 | Type::TYPE_BYTES_VALUE => '$reader->readByteStream($stream)',
202 | ];
203 |
204 | if (isset($mapping[$type])) {
205 | return $mapping[$type];
206 | }
207 |
208 | throw new InvalidArgumentException('Unknown field type : ' . $type);
209 | }
210 |
211 | /**
212 | * @param string $variable
213 | *
214 | * @return string
215 | */
216 | protected function getBreakStatement($variable)
217 | {
218 | if ($this->breakMode === self::BREAK_MODE_CONTINUE) {
219 | return 'continue;';
220 | }
221 |
222 | return 'return ' . $variable . ';';
223 | }
224 | }
225 |
--------------------------------------------------------------------------------