├── tests ├── Resources │ ├── repeated-enum.bin │ ├── php_options.bin │ ├── repeated-int32.bin │ ├── repeated-packed.bin │ ├── extension.bin │ ├── repeated-nested.bin │ ├── repeated-packed-enum.bin │ ├── repeated-bytes.bin │ ├── repeated-int32.txt │ ├── repeated-string.bin │ ├── repeated-string.txt │ ├── tree.bin │ ├── simple.bin │ ├── unknown.bin │ ├── addressbook.bin │ ├── unknown.proto │ ├── extension-animal-cat.bin │ ├── repeated-nested.txt │ ├── extension-command-version.bin │ ├── tree.proto │ ├── tree.txt │ ├── complex.proto │ ├── simple.txt │ ├── addressbook.txt │ ├── repeated.proto │ ├── php_options.proto │ ├── service.proto │ ├── simple.proto │ ├── addressbook.proto │ └── extension.proto ├── Protos │ └── .gitignore ├── Binary │ ├── Platform │ │ ├── BigEndianTest.php │ │ ├── InvalidNegativeEncoderTest.php │ │ ├── BcNegativeEncoderTest.php │ │ ├── PlatformFactoryTest.php │ │ └── GmpNegativeEncoderTest.php │ ├── StreamWriterTest.php │ ├── StreamReaderTest.php │ └── SizeCalculatorTest.php ├── EnumTest.php ├── travis │ └── install-protobuf.sh ├── Extension │ ├── ExtensionRegistryTest.php │ └── ExtensionFieldMapTest.php ├── ConfigurationTest.php ├── StreamCollectionTest.php ├── TestCase.php ├── EnumCollectionTest.php ├── ScalarCollectionTest.php ├── MessageCollectionTest.php ├── FieldTest.php ├── MessageSerializerTest.php ├── TextFormatTest.php ├── StreamTest.php ├── ProtocSerializeMessageTest.php └── SerializeMessageTest.php ├── examples ├── src │ └── .gitignore ├── addressbook.proto └── addressbook-add-person.php ├── src ├── Exception.php ├── Collection.php ├── Extension.php ├── UnknownFieldSet.php ├── Binary │ ├── Platform │ │ ├── NegativeEncoder.php │ │ ├── InvalidNegativeEncoder.php │ │ ├── BigEndian.php │ │ ├── PlatformFactory.php │ │ ├── BcNegativeEncoder.php │ │ └── GmpNegativeEncoder.php │ ├── SizeCalculator.php │ ├── StreamWriter.php │ └── StreamReader.php ├── Serializer.php ├── ComputeSizeContext.php ├── Unknown.php ├── Enum.php ├── EnumCollection.php ├── AbstractMessage.php ├── StreamCollection.php ├── MessageCollection.php ├── MessageSerializer.php ├── ScalarCollection.php ├── Extension │ ├── ExtensionRegistry.php │ ├── ExtensionField.php │ └── ExtensionFieldMap.php ├── WriteContext.php ├── ReadContext.php ├── Message.php ├── TextFormat.php ├── WireFormat.php ├── Field.php ├── Configuration.php └── Stream.php ├── ruleset.xml ├── .gitignore ├── .travis.yml ├── LICENSE ├── phpunit.xml.dist ├── composer.json ├── Makefile └── README.md /tests/Resources/repeated-enum.bin: -------------------------------------------------------------------------------- 1 | 00 -------------------------------------------------------------------------------- /examples/src/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /tests/Protos/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /tests/Resources/php_options.bin: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /tests/Resources/repeated-int32.bin: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /tests/Resources/repeated-packed.bin: -------------------------------------------------------------------------------- 1 | " -------------------------------------------------------------------------------- /tests/Resources/extension.bin: -------------------------------------------------------------------------------- 1 | 2 | FIRSTSECOND -------------------------------------------------------------------------------- /tests/Resources/repeated-nested.bin: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /tests/Resources/repeated-packed-enum.bin: -------------------------------------------------------------------------------- 1 | : -------------------------------------------------------------------------------- /tests/Resources/repeated-bytes.bin: -------------------------------------------------------------------------------- 1 | *bin1*bin2*bin3 -------------------------------------------------------------------------------- /tests/Resources/repeated-int32.txt: -------------------------------------------------------------------------------- 1 | int: 1 2 | int: 2 3 | int: 3 4 | -------------------------------------------------------------------------------- /tests/Resources/repeated-string.bin: -------------------------------------------------------------------------------- 1 | 2 | one 3 | two 4 | three -------------------------------------------------------------------------------- /tests/Resources/repeated-string.txt: -------------------------------------------------------------------------------- 1 | string: "one" 2 | string: "two" 3 | string: "three" 4 | -------------------------------------------------------------------------------- /tests/Resources/tree.bin: -------------------------------------------------------------------------------- 1 | 2 | /Users 3 | /Users/fabio 4 | /Users 5 | /Users/admin 6 | /Users -------------------------------------------------------------------------------- /tests/Resources/simple.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/protobuf-php/protobuf/HEAD/tests/Resources/simple.bin -------------------------------------------------------------------------------- /tests/Resources/unknown.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/protobuf-php/protobuf/HEAD/tests/Resources/unknown.bin -------------------------------------------------------------------------------- /tests/Resources/addressbook.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/protobuf-php/protobuf/HEAD/tests/Resources/addressbook.bin -------------------------------------------------------------------------------- /tests/Resources/unknown.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package ProtobufTest.Protos; 4 | 5 | message Unrecognized { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /tests/Resources/extension-animal-cat.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/protobuf-php/protobuf/HEAD/tests/Resources/extension-animal-cat.bin -------------------------------------------------------------------------------- /tests/Resources/repeated-nested.txt: -------------------------------------------------------------------------------- 1 | nested { 2 | id: 1 3 | } 4 | nested { 5 | id: 2 6 | } 7 | nested { 8 | id: 3 9 | } 10 | -------------------------------------------------------------------------------- /tests/Resources/extension-command-version.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/protobuf-php/protobuf/HEAD/tests/Resources/extension-command-version.bin -------------------------------------------------------------------------------- /tests/Resources/tree.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package ProtobufTest.Protos.Tree; 4 | 5 | message Node { 6 | required string path = 1; 7 | repeated Node children = 2; 8 | optional Node parent = 3; 9 | } 10 | -------------------------------------------------------------------------------- /tests/Resources/tree.txt: -------------------------------------------------------------------------------- 1 | path: "\/Users" 2 | children { 3 | path: "\/Users\/fabio" 4 | parent { 5 | path: "\/Users" 6 | } 7 | } 8 | children { 9 | path: "\/Users\/admin" 10 | parent { 11 | path: "\/Users" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Exception.php: -------------------------------------------------------------------------------- 1 | 9 | * @author Fabio B. Silva 10 | */ 11 | class Exception extends \Exception 12 | { 13 | } 14 | -------------------------------------------------------------------------------- /src/Collection.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | interface Collection extends Countable, Traversable 14 | { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/Extension.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | interface Extension 15 | { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /tests/Resources/complex.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package ProtobufTest.Protos; 4 | 5 | message Complex { 6 | 7 | enum Enum { 8 | FOO = 1; 9 | BAR = 2; 10 | BAZ = 10; 11 | } 12 | 13 | message Nested { 14 | optional string foo = 1; 15 | } 16 | 17 | optional Enum enum = 1; 18 | optional Nested nested = 2; 19 | } 20 | -------------------------------------------------------------------------------- /tests/Resources/simple.txt: -------------------------------------------------------------------------------- 1 | double: 123456789.12345 2 | float: 12345.123 3 | int64: -123456789123456789 4 | uint64: 123456789123456789 5 | int32: -123456789 6 | fixed64: 123456789123456789 7 | fixed32: 123456789 8 | bool: 1 9 | string: "foo" 10 | bytes: "bar" 11 | uint32: 123456789 12 | sfixed32: -123456789 13 | sfixed64: -123456789123456789 14 | sint32: -123456789 15 | sint64: -123456789123456789 16 | -------------------------------------------------------------------------------- /tests/Resources/addressbook.txt: -------------------------------------------------------------------------------- 1 | person { 2 | name: "John Doe" 3 | id: 2051 4 | email: "john.doe@gmail.com" 5 | phone { 6 | number: "1231231212" 7 | type: HOME 8 | } 9 | phone { 10 | number: "55512321312" 11 | type: MOBILE 12 | } 13 | } 14 | person { 15 | name: "Iv\u00e1n Montes" 16 | id: 23 17 | email: "drslump@pollinimini.net" 18 | phone { 19 | number: "3493123123" 20 | type: WORK 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Coding Standard 4 | 5 | */Resources/* 6 | 7 | 8 | 9 | 10 | 11 | 12 | 0 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/Binary/Platform/BigEndianTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($expected, $actual); 17 | } 18 | } -------------------------------------------------------------------------------- /tests/EnumTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(Enum::CLASS) 12 | ->setConstructorArgs(['FOO', 1]) 13 | ->setMethods(['FOO']) 14 | ->getMock(); 15 | 16 | $this->assertEquals(1, $mock->value()); 17 | $this->assertEquals('FOO', $mock->name()); 18 | $this->assertEquals('FOO', $mock->__toString()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Resources/repeated.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package ProtobufTest.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 | repeated Enum packed_enum = 7 [packed=true]; 23 | } 24 | -------------------------------------------------------------------------------- /src/UnknownFieldSet.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class UnknownFieldSet extends ArrayObject implements Collection 13 | { 14 | /** 15 | * Adds an element to set. 16 | * 17 | * @param \Protobuf\Unknown $unknown 18 | */ 19 | public function add(Unknown $unknown) 20 | { 21 | $this->offsetSet($unknown->tag, $unknown); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/addressbook.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package tutorial; 4 | import "php.proto"; 5 | option (php.package) = "Tutorial.AddressBookProtos"; 6 | 7 | message Person { 8 | required string name = 1; 9 | required int32 id = 2; 10 | optional string email = 3; 11 | 12 | enum PhoneType { 13 | MOBILE = 0; 14 | HOME = 1; 15 | WORK = 2; 16 | } 17 | 18 | message PhoneNumber { 19 | required string number = 1; 20 | optional PhoneType type = 2 [default = HOME]; 21 | } 22 | 23 | repeated PhoneNumber phone = 4; 24 | } 25 | 26 | message AddressBook { 27 | repeated Person person = 1; 28 | } 29 | -------------------------------------------------------------------------------- /tests/Resources/php_options.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package ProtobufTest.Protos; 4 | 5 | import "php.proto"; 6 | 7 | option (php.package) = "ProtobufTest.Protos.Options"; 8 | 9 | message ParentMessage 10 | { 11 | enum InnerEnum 12 | { 13 | VALUE1 = 1; 14 | VALUE2 = 2; 15 | } 16 | 17 | message InnerMessage 18 | { 19 | enum InnerMessageEnum 20 | { 21 | VALUE1 = 1; 22 | VALUE2 = 2; 23 | } 24 | 25 | required InnerMessageEnum enum = 1; 26 | } 27 | 28 | required InnerEnum enum = 1; 29 | repeated InnerMessage inner = 2; 30 | } 31 | -------------------------------------------------------------------------------- /tests/Resources/service.proto: -------------------------------------------------------------------------------- 1 | package ProtobufTest.Protos.Service; 2 | 3 | // option java_package = "ProtobufTest.Protos.Service"; 4 | // option java_outer_classname = "ServicePB"; 5 | 6 | message SearchRequest { 7 | required string query = 1; 8 | optional int32 page_number = 2; 9 | optional int32 result_per_page = 3 [default = 10]; 10 | } 11 | 12 | message SearchResponse { 13 | repeated Result result = 1; 14 | } 15 | 16 | message Result { 17 | required string url = 1; 18 | optional string title = 2; 19 | repeated string snippets = 3; 20 | } 21 | 22 | service SearchService { 23 | rpc search (SearchRequest) returns (SearchResponse); 24 | } 25 | -------------------------------------------------------------------------------- /src/Binary/Platform/NegativeEncoder.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface NegativeEncoder 11 | { 12 | /** 13 | * Encode a negative varint. 14 | * 15 | * @param integer $value 16 | * 17 | * @return array 18 | */ 19 | public function encodeVarint($value); 20 | 21 | /** 22 | * Encode an integer as a fixed of 64bits. 23 | * 24 | * @param integer $value 25 | * 26 | * @return string 27 | */ 28 | public function encodeSFixed64($value); 29 | } 30 | -------------------------------------------------------------------------------- /tests/Resources/simple.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package ProtobufTest.Protos; 4 | 5 | message Simple { 6 | optional double double = 1; 7 | optional float float = 2; 8 | optional int64 int64 = 3; 9 | optional uint64 uint64 = 4; 10 | optional int32 int32 = 5; 11 | optional fixed64 fixed64 = 6; 12 | optional fixed32 fixed32 = 7; 13 | optional bool bool = 8; 14 | optional string string = 9; 15 | optional bytes bytes = 12; 16 | optional uint32 uint32 = 13; 17 | optional sfixed32 sfixed32 = 15; 18 | optional sfixed64 sfixed64 = 16; 19 | optional sint32 sint32 = 17; 20 | optional sint64 sint64 = 18; 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .gitignore support plugin (hsz.mobi) 2 | ## JetBrains IDE project directory 3 | .idea/ 4 | nbproject 5 | 6 | ## build 7 | build 8 | generated 9 | docs/build 10 | phpunit.xml 11 | 12 | ## File-based project format 13 | *.ipr 14 | *.iws 15 | 16 | ## Additional for IntelliJ 17 | out/ 18 | 19 | # generated by mpeltonen/sbt-idea plugin 20 | .idea_modules/ 21 | 22 | # generated by JIRA plugin 23 | atlassian-ide-plugin.xml 24 | 25 | # generated by Crashlytics plugin (for Android Studio and Intellij) 26 | com_crashlytics_export_strings.xml 27 | 28 | # ignore composer vendor directory and lock file 29 | vendor 30 | composer.phar 31 | composer.lock 32 | 33 | # ignore mac files 34 | .DS_Store 35 | -------------------------------------------------------------------------------- /src/Serializer.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface Serializer 11 | { 12 | /** 13 | * Serializes the given message. 14 | * 15 | * @param \Protobuf\Message $message 16 | * 17 | * @return \Protobuf\Stream 18 | */ 19 | public function serialize(Message $message); 20 | 21 | /** 22 | * Deserializes the given data to the specified message. 23 | * 24 | * @param string $class 25 | * @param \Protobuf\Stream|resource|string $stream 26 | * 27 | * @return \Protobuf\Message 28 | */ 29 | public function unserialize($class, $stream); 30 | } 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: php 4 | 5 | cache: 6 | directories: 7 | - $HOME/.composer/cache 8 | - $HOME/protobuf 9 | 10 | php: 11 | - 5.5 12 | - 5.6 13 | - 7.0 14 | - hhvm 15 | - nightly 16 | 17 | env: 18 | - PROTOBUF_VERSION=2.6.1 19 | - PROTOBUF_VERSION=3.0.0 20 | 21 | before_install: 22 | - bash ./tests/travis/install-protobuf.sh 23 | 24 | install: 25 | - composer self-update 26 | - composer --prefer-source install 27 | - export PATH=$PATH:$HOME/protobuf/$PROTOBUF_VERSION/bin/ 28 | 29 | script: 30 | - make phpcs proto-generate phpunit-coverage-clover 31 | 32 | after_script: 33 | - ./vendor/bin/coveralls -v 34 | 35 | matrix: 36 | allow_failures: 37 | - php: nightly 38 | -------------------------------------------------------------------------------- /src/ComputeSizeContext.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class ComputeSizeContext 13 | { 14 | /** 15 | * @var \Protobuf\Binary\SizeCalculator 16 | */ 17 | private $calculator; 18 | 19 | /** 20 | * @param \Protobuf\Binary\SizeCalculator $calculator 21 | */ 22 | public function __construct(SizeCalculator $calculator) 23 | { 24 | $this->calculator = $calculator; 25 | } 26 | 27 | /** 28 | * @return \Protobuf\Binary\SizeCalculator 29 | */ 30 | public function getSizeCalculator() 31 | { 32 | return $this->calculator; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Unknown.php: -------------------------------------------------------------------------------- 1 | 9 | * @author Fabio B. Silva 10 | */ 11 | class Unknown 12 | { 13 | /** 14 | * @var integer 15 | */ 16 | public $tag; 17 | 18 | /** 19 | * @var integer 20 | */ 21 | public $type; 22 | 23 | /** 24 | * @var mixed 25 | */ 26 | public $value; 27 | 28 | /** 29 | * @param integer $tag 30 | * @param integer $type 31 | * @param mixed $value 32 | */ 33 | public function __construct($tag = 0, $type = null, $value = null) 34 | { 35 | $this->tag = $tag; 36 | $this->type = $type; 37 | $this->value = $value; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Binary/Platform/InvalidNegativeEncoder.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class InvalidNegativeEncoder implements NegativeEncoder 13 | { 14 | /** 15 | * {@inheritdoc} 16 | */ 17 | public function encodeVarint($varint) 18 | { 19 | throw new RuntimeException("Negative integers are only supported with GMP or BC (64bit) intextensions."); 20 | } 21 | 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public function encodeSFixed64($sFixed64) 26 | { 27 | throw new RuntimeException("Negative integers are only supported with GMP or BC (64bit) intextensions."); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/travis/install-protobuf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | if [ -z "$PROTOBUF_VERSION" ]; then 5 | echo 'PROTOBUF_VERSION env var is not defined.'; 6 | exit 1; 7 | fi 8 | 9 | if [ -d "$HOME/protobuf/$PROTOBUF_VERSION/lib" ]; then 10 | echo 'Using cached instalation.'; 11 | exit 0; 12 | fi 13 | 14 | case "$PROTOBUF_VERSION" in 15 | 2*) 16 | PROTOBUF_RELEASE_FILE=protobuf-$PROTOBUF_VERSION 17 | ;; 18 | 3*) 19 | PROTOBUF_RELEASE_FILE=protobuf-cpp-$PROTOBUF_VERSION 20 | ;; 21 | *) 22 | echo "Unknown protobuf version: $PROTOBUF_VERSION" 23 | exit 1; 24 | ;; 25 | esac 26 | 27 | wget https://github.com/google/protobuf/releases/download/v$PROTOBUF_VERSION/$PROTOBUF_RELEASE_FILE.tar.gz 28 | 29 | tar xf $PROTOBUF_RELEASE_FILE.tar.gz 30 | 31 | cd protobuf-$PROTOBUF_VERSION && ./configure --prefix=$HOME/protobuf/$PROTOBUF_VERSION && make && make install 32 | 33 | -------------------------------------------------------------------------------- /tests/Extension/ExtensionRegistryTest.php: -------------------------------------------------------------------------------- 1 | assertNull($registry->findByNumber(Animal::CLASS, 100)); 19 | 20 | $registry->add($extension); 21 | 22 | $this->assertSame($extension, $registry->findByNumber(Animal::CLASS, 100)); 23 | 24 | $registry->clear(); 25 | 26 | $this->assertNull($registry->findByNumber(Animal::CLASS, 100)); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Resources/addressbook.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package ProtobufTest.Protos; 4 | 5 | /** 6 | * Defines a Person in the addressbook 7 | */ 8 | message Person { 9 | /* The full name of the person */ 10 | required string name = 1; 11 | /* The person Id in the database */ 12 | required int32 id = 2; 13 | /* The person email */ 14 | optional string email = 3; 15 | 16 | /* Different types of phones */ 17 | enum PhoneType { 18 | MOBILE = 0; 19 | HOME = 1; 20 | WORK = 2; 21 | } 22 | 23 | /* 24 | A phone number record 25 | */ 26 | message PhoneNumber { 27 | required string number = 1; 28 | optional PhoneType type = 2 [default = HOME]; 29 | } 30 | 31 | /* The different phone numbers associated to a person */ 32 | repeated PhoneNumber phone = 4; 33 | } 34 | 35 | /* A collection of persons contact details */ 36 | message AddressBook { 37 | repeated Person person = 1; 38 | 39 | extensions 1000 to max; 40 | } 41 | -------------------------------------------------------------------------------- /tests/Binary/Platform/InvalidNegativeEncoderTest.php: -------------------------------------------------------------------------------- 1 | encodeVarint(-1); 19 | } 20 | 21 | /** 22 | * @expectedException RuntimeException 23 | * @expectedExceptionMessage Negative integers are only supported with GMP or BC (64bit) intextensions. 24 | */ 25 | public function testEncodeEncodeSFixed64Exception() 26 | { 27 | $encoder = new InvalidNegativeEncoder(); 28 | 29 | $encoder->encodeSFixed64(-1); 30 | } 31 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/Enum.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | abstract class Enum 15 | { 16 | /** 17 | * Enum value 18 | * 19 | * @var integer 20 | */ 21 | protected $value; 22 | 23 | /** 24 | * Enum name 25 | * 26 | * @var string 27 | */ 28 | protected $name; 29 | 30 | /** 31 | * @param string $name 32 | * @param integer $value 33 | */ 34 | public function __construct($name, $value) 35 | { 36 | $this->name = $name; 37 | $this->value = $value; 38 | } 39 | 40 | /** 41 | * @return int 42 | */ 43 | public function value() 44 | { 45 | return $this->value; 46 | } 47 | 48 | /** 49 | * @return string 50 | */ 51 | public function name() 52 | { 53 | return $this->name; 54 | } 55 | 56 | /** 57 | * @return string 58 | */ 59 | public function __toString() 60 | { 61 | return (string) $this->name; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Binary/Platform/BigEndian.php: -------------------------------------------------------------------------------- 1 | 9 | * @author Fabio B. Silva 10 | */ 11 | class BigEndian 12 | { 13 | /** 14 | * @var bool 15 | */ 16 | protected static $is32Bit; 17 | 18 | /** 19 | * @var integer 20 | */ 21 | protected static $isBigEndian; 22 | 23 | /** 24 | * Check if the current architecture is Big Endian. 25 | * 26 | * @return bool 27 | */ 28 | public static function isBigEndian() 29 | { 30 | if (self::$isBigEndian !== null) { 31 | return self::$isBigEndian; 32 | } 33 | 34 | list(, $result) = unpack('L', pack('V', 1)); 35 | self::$isBigEndian = $result !== 1; 36 | 37 | return self::$isBigEndian; 38 | } 39 | 40 | /** 41 | * @return bool 42 | */ 43 | public static function is32Bit() 44 | { 45 | if (self::$is32Bit !== null) { 46 | self::$is32Bit; 47 | } 48 | 49 | self::$is32Bit = (PHP_INT_SIZE < 8); 50 | 51 | return self::$is32Bit; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/Binary/Platform/BcNegativeEncoderTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('The BC MATH extension is not available.'); 16 | } 17 | } 18 | 19 | public function testEncodeVarint() 20 | { 21 | $encoder = new BcNegativeEncoder(); 22 | $actual = $encoder->encodeVarint(-10); 23 | $expected = [ 24 | 246, 255, 255, 255, 255, 25 | 255, 255, 255, 255, 129, 26 | ]; 27 | 28 | $this->assertEquals($expected, $actual); 29 | } 30 | 31 | public function testEncodeSFixed64() 32 | { 33 | $encoder = new BcNegativeEncoder(); 34 | $bytes = $encoder->encodeSFixed64(-123456789123456789); 35 | $expected = [ 36 | 1 => 41195, 37 | 2 => 21295, 38 | 3 => 25780, 39 | 4 => 65097 40 | ]; 41 | 42 | $this->assertEquals($expected, unpack('v*', $bytes)); 43 | } 44 | } -------------------------------------------------------------------------------- /src/EnumCollection.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class EnumCollection extends ArrayObject implements Collection 14 | { 15 | /** 16 | * @param array<\Protobuf\Enum> $values 17 | */ 18 | public function __construct(array $values = []) 19 | { 20 | array_walk($values, [$this, 'add']); 21 | } 22 | 23 | /** 24 | * Adds a \Protobuf\Enum to this collection 25 | * 26 | * @param \Protobuf\Enum $enum 27 | */ 28 | public function add(Enum $enum) 29 | { 30 | parent::offsetSet(null, $enum); 31 | } 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function offsetSet($offset, $value) 37 | { 38 | if ( ! $value instanceof Enum) { 39 | throw new InvalidArgumentException(sprintf( 40 | 'Argument 2 passed to %s must be a \Protobuf\Enum, %s given', 41 | __METHOD__, 42 | is_object($value) ? get_class($value) : gettype($value) 43 | )); 44 | } 45 | 46 | parent::offsetSet($offset, $value); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/AbstractMessage.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | abstract class AbstractMessage implements Message 13 | { 14 | /** 15 | * Message constructor 16 | * 17 | * @param \Protobuf\Stream|resource|string $stream 18 | * @param \Protobuf\Configuration $configuration 19 | */ 20 | public function __construct($stream = null, \Protobuf\Configuration $configuration = null) 21 | { 22 | if ($stream === null) { 23 | return; 24 | } 25 | 26 | $config = $configuration ?: \Protobuf\Configuration::getInstance(); 27 | $context = $config->createReadContext($stream); 28 | 29 | $this->readFrom($context); 30 | } 31 | 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function __toString() 36 | { 37 | $format = new TextFormat(); 38 | $stream = $format->encodeMessage($this); 39 | 40 | return $stream->__toString(); 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public static function __set_state(array $values) 47 | { 48 | return static::fromArray($values); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/StreamCollection.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class StreamCollection extends ArrayObject implements Collection 14 | { 15 | /** 16 | * @param array<\Protobuf\Stream> $values 17 | */ 18 | public function __construct(array $values = []) 19 | { 20 | array_walk($values, [$this, 'add']); 21 | } 22 | 23 | /** 24 | * Adds a \Protobuf\Stream to this collection 25 | * 26 | * @param \Protobuf\Stream $stream 27 | */ 28 | public function add(Stream $stream) 29 | { 30 | parent::offsetSet(null, $stream); 31 | } 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function offsetSet($offset, $value) 37 | { 38 | if ( ! $value instanceof Stream) { 39 | throw new InvalidArgumentException(sprintf( 40 | 'Argument 2 passed to %s must be a \Protobuf\Stream, %s given', 41 | __METHOD__, 42 | is_object($value) ? get_class($value) : gettype($value) 43 | )); 44 | } 45 | 46 | parent::offsetSet($offset, $value); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/MessageCollection.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class MessageCollection extends ArrayObject implements Collection 14 | { 15 | /** 16 | * @param array<\Protobuf\Message> $values 17 | */ 18 | public function __construct(array $values = []) 19 | { 20 | array_walk($values, [$this, 'add']); 21 | } 22 | 23 | /** 24 | * Adds a message to this collection 25 | * 26 | * @param \Protobuf\Message $message 27 | */ 28 | public function add(Message $message) 29 | { 30 | parent::offsetSet(null, $message); 31 | } 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function offsetSet($offset, $value) 37 | { 38 | if ( ! $value instanceof Message) { 39 | throw new InvalidArgumentException(sprintf( 40 | 'Argument 2 passed to %s must implement interface \Protobuf\Message, %s given', 41 | __METHOD__, 42 | is_object($value) ? get_class($value) : gettype($value) 43 | )); 44 | } 45 | 46 | parent::offsetSet($offset, $value); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/ConfigurationTest.php: -------------------------------------------------------------------------------- 1 | configuration = new Configuration(); 17 | } 18 | 19 | public function testGetAndSetPlatformFactory() 20 | { 21 | $mock = $this->getMock('Protobuf\Binary\Platform\PlatformFactory'); 22 | $factory = $this->configuration->getPlatformFactory(); 23 | 24 | $this->assertInstanceOf('Protobuf\Binary\Platform\PlatformFactory', $factory); 25 | 26 | $this->configuration->setPlatformFactory($mock); 27 | 28 | $this->assertSame($mock, $this->configuration->getPlatformFactory()); 29 | } 30 | 31 | public function testGetAndSetExtensionRegistry() 32 | { 33 | $mock = $this->getMock('Protobuf\Extension\ExtensionRegistry'); 34 | $registry = $this->configuration->getExtensionRegistry(); 35 | 36 | $this->assertInstanceOf('Protobuf\Extension\ExtensionRegistry', $registry); 37 | $this->assertSame($registry, $this->configuration->getExtensionRegistry()); 38 | 39 | $this->configuration->setExtensionRegistry($mock); 40 | 41 | $this->assertSame($mock, $this->configuration->getExtensionRegistry()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Binary/Platform/PlatformFactory.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class PlatformFactory 13 | { 14 | /** 15 | * @var \Protobuf\Binary\Platform\NegativeEncoder 16 | */ 17 | private $negativeEncoder; 18 | 19 | /** 20 | * Return a NegativeEncoder. 21 | * 22 | * @return \Protobuf\Binary\Platform\NegativeEncoder 23 | */ 24 | public function getNegativeEncoder() 25 | { 26 | if ($this->negativeEncoder !== null) { 27 | return $this->negativeEncoder; 28 | } 29 | 30 | if ($this->isExtensionLoaded('gmp')) { 31 | return $this->negativeEncoder = new GmpNegativeEncoder(); 32 | } 33 | 34 | if ($this->isExtensionLoaded('bcmath') && ! $this->is32Bit()) { 35 | return $this->negativeEncoder = new BcNegativeEncoder(); 36 | } 37 | 38 | return $this->negativeEncoder = new InvalidNegativeEncoder(); 39 | } 40 | 41 | /** 42 | * @param string $name 43 | * 44 | * @return boolean 45 | */ 46 | public function isExtensionLoaded($name) 47 | { 48 | return extension_loaded($name); 49 | } 50 | 51 | /** 52 | * @return boolean 53 | */ 54 | public function is32Bit() 55 | { 56 | return BigEndian::is32Bit(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/MessageSerializer.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class MessageSerializer implements Serializer 11 | { 12 | /** 13 | * @var \Protobuf\Configuration 14 | */ 15 | private $config; 16 | 17 | /** 18 | * @param \Protobuf\Configuration $config 19 | */ 20 | public function __construct(Configuration $config = null) 21 | { 22 | $this->config = $config ?: Configuration::getInstance(); 23 | } 24 | 25 | /** 26 | * @return \Protobuf\Configuration 27 | */ 28 | public function getConfiguration() 29 | { 30 | return $this->config; 31 | } 32 | 33 | /** 34 | * Serializes the given message. 35 | * 36 | * @param \Protobuf\Message $message 37 | * 38 | * @return \Protobuf\Stream 39 | */ 40 | public function serialize(Message $message) 41 | { 42 | return $message->toStream($this->config); 43 | } 44 | 45 | /** 46 | * Deserializes the given data to the specified message. 47 | * 48 | * @param string $class 49 | * @param \Protobuf\Stream|resource|string $stream 50 | * 51 | * @return \Protobuf\Message 52 | */ 53 | public function unserialize($class, $stream) 54 | { 55 | return new $class($stream, $this->config); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/ScalarCollection.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class ScalarCollection extends ArrayObject implements Collection 14 | { 15 | /** 16 | * @param array $values 17 | */ 18 | public function __construct(array $values = []) 19 | { 20 | array_walk($values, [$this, 'add']); 21 | } 22 | 23 | /** 24 | * Adds a value to this collection 25 | * 26 | * @param scalar $value 27 | */ 28 | public function add($value) 29 | { 30 | if ( ! is_scalar($value)) { 31 | throw new InvalidArgumentException(sprintf( 32 | 'Argument 1 passed to %s must be a scalar value, %s given', 33 | __METHOD__, 34 | is_object($value) ? get_class($value) : gettype($value) 35 | )); 36 | } 37 | 38 | parent::offsetSet(null, $value); 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function offsetSet($offset, $value) 45 | { 46 | if ( ! is_scalar($value)) { 47 | throw new InvalidArgumentException(sprintf( 48 | 'Argument 2 passed to %s must be a scalar value, %s given', 49 | __METHOD__, 50 | is_object($value) ? get_class($value) : gettype($value) 51 | )); 52 | } 53 | 54 | parent::offsetSet($offset, $value); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Extension/ExtensionRegistry.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ExtensionRegistry 11 | { 12 | /** 13 | * @var array 14 | */ 15 | protected $extensions = []; 16 | 17 | /** 18 | * Remove all registered extensions 19 | */ 20 | public function clear() 21 | { 22 | $this->extensions = []; 23 | } 24 | 25 | /** 26 | * Adds an element to the registry. 27 | * 28 | * @param \Protobuf\Extension\ExtensionField $extension 29 | */ 30 | public function add(ExtensionField $extension) 31 | { 32 | $extendee = trim($extension->getExtendee(), '\\'); 33 | $number = $extension->getTag(); 34 | 35 | if ( ! isset($this->extensions[$extendee])) { 36 | $this->extensions[$extendee] = []; 37 | } 38 | 39 | $this->extensions[$extendee][$number] = $extension; 40 | } 41 | 42 | /** 43 | * Find an extension by containing field number 44 | * 45 | * @param string $className 46 | * @param integer $number 47 | * 48 | * @return \Protobuf\Extension\ExtensionField|null 49 | */ 50 | public function findByNumber($className, $number) 51 | { 52 | $extendee = trim($className, '\\'); 53 | 54 | if ( ! isset($this->extensions[$extendee][$number])) { 55 | return null; 56 | } 57 | 58 | return $this->extensions[$extendee][$number]; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/Resources/extension.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package ProtobufTest.Protos.Extension; 4 | 5 | option java_multiple_files = true; 6 | 7 | // Animal 8 | 9 | message Animal 10 | { 11 | extensions 100 to max; 12 | 13 | enum Type 14 | { 15 | CAT = 1; 16 | DOG = 2; 17 | } 18 | 19 | required Type type = 1; 20 | } 21 | 22 | extend Animal 23 | { 24 | optional string habitat = 200; 25 | } 26 | 27 | message Cat 28 | { 29 | extend Animal 30 | { 31 | optional Cat animal = 100; 32 | } 33 | 34 | optional bool declawed = 1; 35 | } 36 | 37 | message Dog 38 | { 39 | extend Animal 40 | { 41 | optional Dog animal = 102; 42 | } 43 | 44 | optional uint32 bones_buried = 2; 45 | } 46 | 47 | // command 48 | 49 | message Command 50 | { 51 | extensions 100 to max; 52 | 53 | enum CommandType 54 | { 55 | VERSION = 1; 56 | LOGIN = 2; 57 | } 58 | 59 | required CommandType type = 1; 60 | } 61 | 62 | extend Command 63 | { 64 | optional bool verbose = 200 [default = false]; 65 | } 66 | 67 | message VersionCommand 68 | { 69 | extend Command 70 | { 71 | optional VersionCommand cmd = 100; 72 | } 73 | 74 | enum Protocol 75 | { 76 | V1 = 1; 77 | V2 = 2; 78 | } 79 | 80 | required Protocol protocol = 1; 81 | required fixed32 version = 2; 82 | } 83 | 84 | message LoginCommand 85 | { 86 | extend Command 87 | { 88 | optional LoginCommand cmd = 101; 89 | } 90 | 91 | required string username = 1; 92 | required string password = 2; 93 | } -------------------------------------------------------------------------------- /src/WriteContext.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class WriteContext 15 | { 16 | /** 17 | * @var \Protobuf\ComputeSizeContext 18 | */ 19 | private $computeSizeContext; 20 | 21 | /** 22 | * @var \Protobuf\Binary\StreamWriter 23 | */ 24 | private $writer; 25 | 26 | /** 27 | * @var \Protobuf\Stream 28 | */ 29 | private $stream; 30 | 31 | /** 32 | * @var integer 33 | */ 34 | private $length; 35 | 36 | /** 37 | * @param \Protobuf\Stream $stream 38 | * @param \Protobuf\Binary\StreamWriter $writer 39 | * @param \Protobuf\ComputeSizeContext $computeSizeContext 40 | */ 41 | public function __construct(Stream $stream, StreamWriter $writer, ComputeSizeContext $computeSizeContext) 42 | { 43 | $this->stream = $stream; 44 | $this->writer = $writer; 45 | $this->computeSizeContext = $computeSizeContext; 46 | } 47 | 48 | /** 49 | * @return \Protobuf\Binary\StreamWriter 50 | */ 51 | public function getWriter() 52 | { 53 | return $this->writer; 54 | } 55 | 56 | /** 57 | * @return \Protobuf\Stream 58 | */ 59 | public function getStream() 60 | { 61 | return $this->stream; 62 | } 63 | 64 | /** 65 | * @return \Protobuf\ComputeSizeContext 66 | */ 67 | public function getComputeSizeContext() 68 | { 69 | return $this->computeSizeContext; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "protobuf-php/protobuf", 3 | "description" : "PHP implementation of Google's Protocol Buffers", 4 | "keywords" : ["protobuf", "protocol buffer", "serializing"], 5 | "homepage" : "https://github.com/protobuf-php/protobuf", 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 | "name": "Iván -DrSlump- Montes", 17 | "email": "drslump@pollinimini.net", 18 | "homepage": "https://github.com/drslump" 19 | } 20 | ], 21 | "require" : { 22 | "ext-mbstring" : "*", 23 | "php" : ">=5.5.0" 24 | }, 25 | "require-dev": { 26 | "protobuf-php/protobuf-plugin" : ">=0.1", 27 | "phpunit/phpunit" : "^4", 28 | "instaclick/coding-standard" : "^1.1", 29 | "squizlabs/php_codesniffer" : "^1.5", 30 | "satooshi/php-coveralls" : "^0.6", 31 | "php-mock/php-mock-phpunit" : "^0.2", 32 | "instaclick/object-calisthenics-sniffs" : "dev-master", 33 | "instaclick/symfony2-coding-standard" : "dev-remaster" 34 | }, 35 | "autoload" : { 36 | "psr-4": { 37 | "Protobuf\\": "src/" 38 | } 39 | }, 40 | "autoload-dev": { 41 | "psr-4": { 42 | "ProtobufTest\\": "tests/" 43 | } 44 | }, 45 | "extra": { 46 | "branch-alias": { 47 | "dev-master": "0.1.x-dev" 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Binary/Platform/BcNegativeEncoder.php: -------------------------------------------------------------------------------- 1 | 11 | * @author Fabio B. Silva 12 | */ 13 | class BcNegativeEncoder implements NegativeEncoder 14 | { 15 | /** 16 | * {@inheritdoc} 17 | */ 18 | public function encodeVarint($varint) 19 | { 20 | $values = []; 21 | $value = sprintf('%u', $varint); 22 | 23 | while (bccomp($value, 0, 0) > 0) { 24 | // Get the last 7bits of the number 25 | $bin = ''; 26 | $dec = $value; 27 | 28 | do { 29 | $rest = bcmod($dec, 2); 30 | $dec = bcdiv($dec, 2, 0); 31 | $bin = $rest . $bin; 32 | } while ($dec > 0 && mb_strlen($bin, '8bit') < 7); 33 | 34 | // Pack as a decimal and apply the flag 35 | $values[] = intval($bin, 2) | 0x80; 36 | $value = bcdiv($value, 0x80, 0); 37 | } 38 | 39 | return $values; 40 | } 41 | 42 | /** 43 | * {@inheritdoc} 44 | */ 45 | public function encodeSFixed64($sFixed64) 46 | { 47 | $value = sprintf('%u', $sFixed64); 48 | $bytes = ''; 49 | 50 | for ($i = 0; $i < 8; ++$i) { 51 | // Get the last 8bits of the number 52 | $bin = ''; 53 | $dec = $value; 54 | 55 | do { 56 | $bin = bcmod($dec, 2).$bin; 57 | $dec = bcdiv($dec, 2, 0); 58 | } while (mb_strlen($bin, '8bit') < 8); 59 | 60 | // Pack the byte 61 | $bytes .= chr(intval($bin, 2)); 62 | $value = bcdiv($value, 0x100, 0); 63 | } 64 | 65 | return $bytes; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # vim: ts=4:sw=4:noexpandtab!: 2 | 3 | BASEDIR := $(shell pwd) 4 | COMPOSER := $(shell which composer) 5 | 6 | help: 7 | @echo "---------------------------------------------" 8 | @echo "List of available targets:" 9 | @echo " composer-install - Installs composer dependencies." 10 | @echo " proto-generate - Generate PHP classes from proto files." 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: 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 | php $(BASEDIR)/vendor/bin/protobuf --include-descriptors \ 39 | --psr4 ProtobufTest\\Protos \ 40 | -o $(BASEDIR)/tests/Protos \ 41 | -i $(BASEDIR)/tests/Resources \ 42 | $(BASEDIR)/tests/Resources/*.proto 43 | 44 | phpunit: proto-generate 45 | php $(BASEDIR)/vendor/bin/phpunit -v; 46 | 47 | phpunit-coverage-clover: 48 | php $(BASEDIR)/vendor/bin/phpunit -v --coverage-clover ./build/logs/clover.xml; 49 | 50 | phpunit-coverage-html: 51 | php $(BASEDIR)/vendor/bin/phpunit -v --coverage-html ./build/coverage; 52 | 53 | phpcs: 54 | php $(BASEDIR)/vendor/bin/phpcs -p --extensions=php --standard=ruleset.xml src; 55 | 56 | .PHONY: composer-install phpunit phpcs help -------------------------------------------------------------------------------- /tests/StreamCollectionTest.php: -------------------------------------------------------------------------------- 1 | collection = new StreamCollection(); 18 | } 19 | 20 | public function testCreateStreamCollection() 21 | { 22 | $stream1 = Stream::create(); 23 | $stream2 = Stream::create(); 24 | 25 | $collection = new StreamCollection([$stream1, $stream2]); 26 | 27 | $this->assertCount(2, $collection); 28 | $this->assertEquals([$stream1, $stream2], $collection->getArrayCopy()); 29 | } 30 | 31 | public function testAddStream() 32 | { 33 | $this->assertCount(0, $this->collection); 34 | 35 | $stream1 = Stream::create(); 36 | $stream2 = Stream::create(); 37 | 38 | $this->collection[] = $stream1; 39 | 40 | $this->collection->add($stream2); 41 | 42 | $this->assertCount(2, $this->collection); 43 | $this->assertEquals([$stream1, $stream2], $this->collection->getArrayCopy()); 44 | } 45 | 46 | /** 47 | * @expectedException InvalidArgumentException 48 | * @expectedExceptionMessage Argument 2 passed to Protobuf\StreamCollection::offsetSet must be a \Protobuf\Stream, stdClass given 49 | */ 50 | public function testInvalidArgumentExceptionOffsetSetObject() 51 | { 52 | $this->collection[] = new \stdClass(); 53 | } 54 | 55 | /** 56 | * @expectedException InvalidArgumentException 57 | * @expectedExceptionMessage Argument 2 passed to Protobuf\StreamCollection::offsetSet must be a \Protobuf\Stream, integer given 58 | */ 59 | public function testInvalidArgumentExceptionOffsetSetInteger() 60 | { 61 | $this->collection[] = 123; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | config = new Configuration(); 18 | } 19 | 20 | /** 21 | * @param object $object 22 | * @param string $method 23 | * @param array $args 24 | * 25 | * @return mixed 26 | */ 27 | protected function invokeMethod($object, $method, array $args= []) 28 | { 29 | $reflection = new \ReflectionMethod($object, $method); 30 | 31 | $reflection->setAccessible(true); 32 | 33 | return $reflection->invokeArgs($object, $args); 34 | } 35 | 36 | /** 37 | * @param object $object 38 | * @param string $property 39 | * 40 | * @return mixed 41 | */ 42 | protected function getPropertyValue($object, $property) 43 | { 44 | $reflection = new \ReflectionProperty($object, $property); 45 | 46 | $reflection->setAccessible(true); 47 | 48 | return $reflection->getValue($object); 49 | } 50 | 51 | /** 52 | * @param object $object 53 | * @param string $property 54 | * @param mixed $value 55 | */ 56 | protected function setPropertyValue($object, $property, $value) 57 | { 58 | $reflection = new \ReflectionProperty($object, $property); 59 | 60 | $reflection->setAccessible(true); 61 | $reflection->setValue($object, $value); 62 | } 63 | 64 | /** 65 | * @param string $name 66 | * 67 | * @return string 68 | */ 69 | protected function getProtoContent($name) 70 | { 71 | $basepath = __DIR__ . '/Resources'; 72 | $content = file_get_contents($basepath . '/' . $name); 73 | 74 | return $content; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/EnumCollectionTest.php: -------------------------------------------------------------------------------- 1 | collection = new EnumCollection(); 18 | } 19 | 20 | public function testCreateEnumCollection() 21 | { 22 | $enum1 = $this->getMock('Protobuf\Enum', [], ['E1', 1]); 23 | $enum2 = $this->getMock('Protobuf\Enum', [], ['E2', 2]); 24 | 25 | $collection = new EnumCollection([$enum1, $enum2]); 26 | 27 | $this->assertCount(2, $collection); 28 | $this->assertEquals([$enum1, $enum2], $collection->getArrayCopy()); 29 | } 30 | 31 | public function testAddEnum() 32 | { 33 | $this->assertCount(0, $this->collection); 34 | 35 | $enum1 = $this->getMock('Protobuf\Enum', [], ['E1', 1]); 36 | $enum2 = $this->getMock('Protobuf\Enum', [], ['E2', 2]); 37 | 38 | $this->collection[] = $enum1; 39 | 40 | $this->collection->add($enum2); 41 | 42 | $this->assertCount(2, $this->collection); 43 | $this->assertEquals([$enum1, $enum2], $this->collection->getArrayCopy()); 44 | } 45 | 46 | /** 47 | * @expectedException InvalidArgumentException 48 | * @expectedExceptionMessage Argument 2 passed to Protobuf\EnumCollection::offsetSet must be a \Protobuf\Enum, stdClass given 49 | */ 50 | public function testInvalidArgumentExceptionOffsetSetObject() 51 | { 52 | $this->collection[] = new \stdClass(); 53 | } 54 | 55 | /** 56 | * @expectedException InvalidArgumentException 57 | * @expectedExceptionMessage Argument 2 passed to Protobuf\EnumCollection::offsetSet must be a \Protobuf\Enum, integer given 58 | */ 59 | public function testInvalidArgumentExceptionOffsetSetInteger() 60 | { 61 | $this->collection[] = 123; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/ScalarCollectionTest.php: -------------------------------------------------------------------------------- 1 | collection = new ScalarCollection(); 17 | } 18 | 19 | public function testCreateScalarCollection() 20 | { 21 | $collection = new ScalarCollection([1,2]); 22 | 23 | $this->assertCount(2, $collection); 24 | $this->assertEquals([1, 2], $collection->getArrayCopy()); 25 | } 26 | 27 | public function testAddValue() 28 | { 29 | $this->assertCount(0, $this->collection); 30 | 31 | $this->collection[] = 1; 32 | 33 | $this->collection->add(2); 34 | 35 | $this->assertCount(2, $this->collection); 36 | $this->assertEquals([1, 2], $this->collection->getArrayCopy()); 37 | } 38 | 39 | /** 40 | * @expectedException InvalidArgumentException 41 | * @expectedExceptionMessage Argument 1 passed to Protobuf\ScalarCollection::add must be a scalar value, stdClass given 42 | */ 43 | public function testInvalidArgumentExceptionAddObject() 44 | { 45 | $this->collection->add(new \stdClass()); 46 | } 47 | 48 | /** 49 | * @expectedException InvalidArgumentException 50 | * @expectedExceptionMessage Argument 2 passed to Protobuf\ScalarCollection::offsetSet must be a scalar value, stdClass given 51 | */ 52 | public function testInvalidArgumentExceptionOffsetSetObject() 53 | { 54 | $this->collection[] = new \stdClass(); 55 | } 56 | 57 | /** 58 | * @expectedException InvalidArgumentException 59 | * @expectedExceptionMessage Argument 2 passed to Protobuf\ScalarCollection::offsetSet must be a scalar value, array given 60 | */ 61 | public function testInvalidArgumentExceptionOffsetSetInteger() 62 | { 63 | $this->collection[] = []; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/MessageCollectionTest.php: -------------------------------------------------------------------------------- 1 | collection = new MessageCollection(); 18 | } 19 | 20 | public function testCreateMessageCollection() 21 | { 22 | $messge1 = $this->getMock(Message::CLASS); 23 | $messge2 = $this->getMock(Message::CLASS); 24 | 25 | $collection = new MessageCollection([$messge1, $messge2]); 26 | 27 | $this->assertCount(2, $collection); 28 | $this->assertEquals([$messge1, $messge2], $collection->getArrayCopy()); 29 | } 30 | 31 | public function testAddMessage() 32 | { 33 | $this->assertCount(0, $this->collection); 34 | 35 | $messge1 = $this->getMock(Message::CLASS); 36 | $messge2 = $this->getMock(Message::CLASS); 37 | 38 | $this->collection[] = $messge1; 39 | 40 | $this->collection->add($messge2); 41 | 42 | $this->assertCount(2, $this->collection); 43 | $this->assertEquals([$messge1, $messge2], $this->collection->getArrayCopy()); 44 | } 45 | 46 | /** 47 | * @expectedException InvalidArgumentException 48 | * @expectedExceptionMessage Argument 2 passed to Protobuf\MessageCollection::offsetSet must implement interface \Protobuf\Message, stdClass given 49 | */ 50 | public function testInvalidArgumentExceptionOffsetSetObject() 51 | { 52 | $this->collection[] = new \stdClass(); 53 | } 54 | 55 | /** 56 | * @expectedException InvalidArgumentException 57 | * @expectedExceptionMessage Argument 2 passed to Protobuf\MessageCollection::offsetSet must implement interface \Protobuf\Message, integer given 58 | */ 59 | public function testInvalidArgumentExceptionOffsetSetInteger() 60 | { 61 | $this->collection[] = 123; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/ReadContext.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class ReadContext 15 | { 16 | /** 17 | * @var \Protobuf\Extension\ExtensionRegistry 18 | */ 19 | private $extensionRegistry; 20 | 21 | /** 22 | * @var \Protobuf\Binary\StreamReader 23 | */ 24 | private $reader; 25 | 26 | /** 27 | * @var \Protobuf\Stream 28 | */ 29 | private $stream; 30 | 31 | /** 32 | * @var integer 33 | */ 34 | private $length; 35 | 36 | /** 37 | * @param \Protobuf\Stream|resource|string $stream 38 | * @param \Protobuf\Binary\StreamReader $reader 39 | * @param \Protobuf\Extension\ExtensionRegistry $extensionRegistry 40 | */ 41 | public function __construct($stream, StreamReader $reader, ExtensionRegistry $extensionRegistry = null) 42 | { 43 | if ( ! $stream instanceof \Protobuf\Stream) { 44 | $stream = Stream::wrap($stream); 45 | } 46 | 47 | $this->stream = $stream; 48 | $this->reader = $reader; 49 | $this->extensionRegistry = $extensionRegistry; 50 | } 51 | 52 | /** 53 | * Return a ExtensionRegistry. 54 | * 55 | * @return \Protobuf\Extension\ExtensionRegistry 56 | */ 57 | public function getExtensionRegistry() 58 | { 59 | return $this->extensionRegistry; 60 | } 61 | 62 | /** 63 | * @return \Protobuf\Binary\StreamReader 64 | */ 65 | public function getReader() 66 | { 67 | return $this->reader; 68 | } 69 | 70 | /** 71 | * @return \Protobuf\Stream 72 | */ 73 | public function getStream() 74 | { 75 | return $this->stream; 76 | } 77 | 78 | /** 79 | * @return integer 80 | */ 81 | public function getLength() 82 | { 83 | return $this->length; 84 | } 85 | 86 | /** 87 | * @param integer $length 88 | */ 89 | public function setLength($length) 90 | { 91 | $this->length = $length; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /tests/Binary/Platform/PlatformFactoryTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('The GMP extension is not available.'); 14 | } 15 | 16 | $factory = $this->getMockBuilder(PlatformFactory::CLASS) 17 | ->setMethods(['isExtensionLoaded']) 18 | ->getMock(); 19 | 20 | $factory->expects($this->once()) 21 | ->method('isExtensionLoaded') 22 | ->with($this->equalTo('gmp')) 23 | ->willReturn(true); 24 | 25 | $this->assertInstanceOf('Protobuf\Binary\Platform\GmpNegativeEncoder', $factory->getNegativeEncoder()); 26 | } 27 | 28 | public function testGetBcNegativeEncoder() 29 | { 30 | if ( ! extension_loaded('bcmath')) { 31 | $this->markTestSkipped('The BC MATH extension is not available.'); 32 | } 33 | 34 | $factory = $this->getMockBuilder(PlatformFactory::CLASS) 35 | ->setMethods(['isExtensionLoaded']) 36 | ->getMock(); 37 | 38 | $factory->expects($this->exactly(2)) 39 | ->method('isExtensionLoaded') 40 | ->will($this->returnValueMap([ 41 | ['gmp', false], 42 | ['bcmath', true] 43 | ])); 44 | 45 | $this->assertInstanceOf('Protobuf\Binary\Platform\BcNegativeEncoder', $factory->getNegativeEncoder()); 46 | } 47 | 48 | public function testGetInvalidNegativeEncoder() 49 | { 50 | $factory = $this->getMockBuilder(PlatformFactory::CLASS) 51 | ->setMethods(['isExtensionLoaded']) 52 | ->getMock(); 53 | 54 | $factory->expects($this->exactly(2)) 55 | ->method('isExtensionLoaded') 56 | ->will($this->returnValueMap([ 57 | ['gmp', false], 58 | ['bcmath', false] 59 | ])); 60 | 61 | $this->assertInstanceOf('Protobuf\Binary\Platform\InvalidNegativeEncoder', $factory->getNegativeEncoder()); 62 | } 63 | } -------------------------------------------------------------------------------- /src/Binary/Platform/GmpNegativeEncoder.php: -------------------------------------------------------------------------------- 1 | 9 | * @author Fabio B. Silva 10 | */ 11 | class GmpNegativeEncoder implements NegativeEncoder 12 | { 13 | /** 14 | * @var \GMP 15 | */ 16 | protected $gmp_x00; 17 | 18 | /** 19 | * @var \GMP 20 | */ 21 | protected $gmp_x7f; 22 | 23 | /** 24 | * @var \GMP 25 | */ 26 | protected $gmp_x80; 27 | 28 | /** 29 | * @var \GMP 30 | */ 31 | protected $gmp_xff; 32 | 33 | /** 34 | * @var \GMP 35 | */ 36 | protected $gmp_x100; 37 | 38 | /** 39 | * @var bool 40 | */ 41 | protected $is32Bit; 42 | 43 | /** 44 | * Constructor 45 | */ 46 | public function __construct() 47 | { 48 | $this->gmp_x00 = gmp_init(0x00); 49 | $this->gmp_x7f = gmp_init(0x7f); 50 | $this->gmp_x80 = gmp_init(0x80); 51 | $this->gmp_xff = gmp_init(0xff); 52 | $this->gmp_x100 = gmp_init(0x100); 53 | $this->is32Bit = BigEndian::is32Bit(); 54 | } 55 | 56 | /** 57 | * {@inheritdoc} 58 | */ 59 | public function encodeVarint($varint) 60 | { 61 | $bytes = []; 62 | $value = $this->is32Bit 63 | ? gmp_and($varint, '0x0ffffffffffffffff') 64 | : sprintf('%u', $varint); 65 | 66 | while (gmp_cmp($value, $this->gmp_x00) > 0) { 67 | $bytes[] = gmp_intval(gmp_and($value, $this->gmp_x7f)) | 0x80; 68 | $value = gmp_div_q($value, $this->gmp_x80); 69 | } 70 | 71 | return $bytes; 72 | } 73 | 74 | /** 75 | * {@inheritdoc} 76 | */ 77 | public function encodeSFixed64($sFixed64) 78 | { 79 | $value = $this->is32Bit 80 | ? gmp_and($sFixed64, '0x0ffffffffffffffff') 81 | : gmp_init(sprintf('%u', $sFixed64)); 82 | 83 | $bytes = ''; 84 | 85 | for ($i = 0; $i < 8; ++$i) { 86 | $bytes .= chr(gmp_intval(gmp_and($value, $this->gmp_xff))); 87 | $value = gmp_div_q($value, $this->gmp_x100); 88 | } 89 | 90 | return $bytes; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /tests/Extension/ExtensionFieldMapTest.php: -------------------------------------------------------------------------------- 1 | assertCount(0, $extensions); 22 | 23 | $extensions->put($extension, $animal); 24 | 25 | $this->assertCount(1, $extensions); 26 | $this->assertTrue($extensions->contains($extension)); 27 | $this->assertSame($animal, $extensions->offsetGet($extension)); 28 | } 29 | 30 | public function testAddMergeExtensions() 31 | { 32 | $animal1 = new Cat(); 33 | $animal2 = new Cat(); 34 | $callback = function () {}; 35 | $extensions = new ExtensionFieldMap(Animal::CLASS); 36 | $extension = new ExtensionField(Animal::CLASS, 'animal', 100, $callback, $callback, $callback); 37 | 38 | $animal1->setDeclawed(true); 39 | 40 | $extensions->add($extension, $animal1); 41 | $this->assertSame($animal1, $extensions->offsetGet($extension)); 42 | 43 | $animal2->setDeclawed(false); 44 | 45 | $extensions->add($extension, $animal2); 46 | $this->assertTrue($animal2->getDeclawed()); 47 | $this->assertSame($animal2, $extensions->offsetGet($extension)); 48 | } 49 | 50 | /** 51 | * @expectedException InvalidArgumentException 52 | * @expectedExceptionMessage Invalid extendee, ProtobufTest\Protos\Extension\Animal is expected but ProtobufTest\Protos\Extension\Cat given 53 | */ 54 | public function testInvalidArgumentExceptionExtendee() 55 | { 56 | $animal = new Cat(); 57 | $extensions = new ExtensionFieldMap(Animal::CLASS); 58 | $extension = new ExtensionField(Cat::CLASS, 'animal', 200, function () {}, function () {}, function () {}); 59 | 60 | $extensions->put($extension, $animal); 61 | } 62 | } -------------------------------------------------------------------------------- /examples/addressbook-add-person.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | add('Tutorial\AddressBookProtos', __DIR__ . '/src'); 7 | 8 | use Tutorial\AddressBookProtos\Person; 9 | use Tutorial\AddressBookProtos\AddressBook; 10 | 11 | if ( ! class_exists('\Tutorial\AddressBookProtos\Person')) { 12 | 13 | fwrite(STDERR, 14 | 'You need to generate the php classes using the following command:' . PHP_EOL . 15 | './vendor/bin/protobuf --include-descriptors -i ./examples/ -o ./examples/src/ ./examples/addressbook.proto' . PHP_EOL 16 | ); 17 | 18 | exit(1); 19 | } 20 | 21 | if ( ! isset($argv[1])) { 22 | echo "Usage: ./examples/addressbook-add-person.php ADDRESS_BOOK_FILE" . PHP_EOL; 23 | exit(1); 24 | } 25 | 26 | // Read the existing address book or create a new one. 27 | $addressBook = is_file($argv[1]) 28 | ? new AddressBook(file_get_contents($argv[1])) 29 | : new AddressBook(); 30 | 31 | $person = new Person(); 32 | $id = intval(readline("Enter person ID: ")); 33 | $name = trim(readline("Enter person name: ")); 34 | $email = trim(readline("Enter email address (blank for none): ")); 35 | 36 | $person->setId($id); 37 | $person->setName($name); 38 | 39 | if ( ! empty($email)) { 40 | $person->setEmail($email); 41 | } 42 | 43 | while (true) { 44 | $number = trim(readline("Enter a phone number (or leave blank to finish):")); 45 | 46 | if (empty($number)) { 47 | break; 48 | } 49 | 50 | $phone = new Person\PhoneNumber(); 51 | $type = trim(readline("Is this a mobile, home, or work phone? ")); 52 | 53 | switch (strtolower($type)) { 54 | case 'mobile': 55 | $phone->setType(Person\PhoneType::MOBILE()); 56 | break; 57 | case 'work': 58 | $phone->setType(Person\PhoneType::WORK()); 59 | break; 60 | case 'home': 61 | $phone->setType(Person\PhoneType::HOME()); 62 | break; 63 | default: 64 | echo "Unknown phone type. Using default." . PHP_EOL; 65 | } 66 | 67 | $phone->setNumber($number); 68 | $person->addPhone($phone); 69 | } 70 | 71 | // Add a person. 72 | $addressBook->addPerson($person); 73 | 74 | // Print current address book 75 | echo $addressBook; 76 | 77 | // Write the new address book back to disk. 78 | file_put_contents($argv[1], $addressBook->toStream()); -------------------------------------------------------------------------------- /src/Message.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | interface Message 16 | { 17 | /** 18 | * Creates message from the given stream. 19 | * 20 | * @param \Protobuf\Stream|resource|string $stream 21 | * @param \Protobuf\Configuration $configuration 22 | * 23 | * @return \Protobuf\Message 24 | */ 25 | public static function fromStream($stream, Configuration $configuration = null); 26 | 27 | /** 28 | * Serializes the message and returns a stream containing its bytes. 29 | * 30 | * @param \Protobuf\Configuration $configuration 31 | * 32 | * @return \Protobuf\Stream 33 | */ 34 | public function toStream(Configuration $configuration = null); 35 | 36 | /** 37 | * Compute the number of bytes that would be needed to encode the message 38 | * 39 | * @param \Protobuf\ComputeSizeContext $context 40 | * 41 | * @return integer 42 | */ 43 | public function serializedSize(ComputeSizeContext $context); 44 | 45 | /** 46 | * Serializes the message and returns a stream containing its bytes. 47 | * 48 | * @param \Protobuf\ReadContext $context 49 | */ 50 | public function readFrom(ReadContext $context); 51 | 52 | /** 53 | * Encodes and writes the message 54 | * 55 | * @param \Protobuf\ReadContext $context 56 | * 57 | * @return \Protobuf\Stream 58 | */ 59 | public function writeTo(WriteContext $context); 60 | 61 | /** 62 | * Merge $context into the message being built. 63 | * 64 | * @param \Protobuf\Message $message 65 | */ 66 | public function merge(Message $message); 67 | 68 | /** 69 | * Obtain the list of unknown fields in this message. 70 | * 71 | * @return \Protobuf\UnknownFieldSet 72 | */ 73 | public function unknownFieldSet(); 74 | 75 | /** 76 | * Obtain the map of extensions in this message. 77 | * 78 | * @return \Protobuf\Extension\ExtensionFieldMap 79 | */ 80 | public function extensions(); 81 | 82 | /** 83 | * Reset all fields back to the initial values. 84 | */ 85 | public function clear(); 86 | } 87 | -------------------------------------------------------------------------------- /tests/Binary/Platform/GmpNegativeEncoderTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('The GMP extension is not available.'); 17 | } 18 | } 19 | 20 | public function testConstructInitializeGmpValues() 21 | { 22 | $encoder = new GmpNegativeEncoder(); 23 | $gmp_x00 = $this->getPropertyValue($encoder, 'gmp_x00'); 24 | $gmp_x7f = $this->getPropertyValue($encoder, 'gmp_x7f'); 25 | $gmp_x80 = $this->getPropertyValue($encoder, 'gmp_x80'); 26 | $gmp_xff = $this->getPropertyValue($encoder, 'gmp_xff'); 27 | $gmp_x100 = $this->getPropertyValue($encoder, 'gmp_x100'); 28 | $is32Bit = $this->getPropertyValue($encoder, 'is32Bit'); 29 | 30 | $this->assertNotNull($gmp_x00); 31 | $this->assertNotNull($gmp_x7f); 32 | $this->assertNotNull($gmp_x80); 33 | $this->assertNotNull($gmp_xff); 34 | $this->assertNotNull($gmp_x100); 35 | 36 | $this->assertEquals(0, gmp_intval($gmp_x00)); 37 | $this->assertEquals(127, gmp_intval($gmp_x7f)); 38 | $this->assertEquals(128, gmp_intval($gmp_x80)); 39 | $this->assertEquals(255, gmp_intval($gmp_xff)); 40 | $this->assertEquals(256, gmp_intval($gmp_x100)); 41 | $this->assertEquals(BigEndian::is32Bit(), $is32Bit); 42 | } 43 | 44 | public function testEncodeVarint() 45 | { 46 | $encoder = new GmpNegativeEncoder(); 47 | 48 | // make sure runs as 64 bit 49 | $this->setPropertyValue($encoder, 'is32Bit', false); 50 | 51 | $actual = $encoder->encodeVarint(-10); 52 | $expected = [ 53 | 246, 255, 255, 255, 255, 54 | 255, 255, 255, 255, 129, 55 | ]; 56 | 57 | $this->assertEquals($expected, $actual); 58 | } 59 | 60 | public function testEncodeSFixed64() 61 | { 62 | $encoder = new GmpNegativeEncoder(); 63 | 64 | // make sure runs as 64 bit 65 | $this->setPropertyValue($encoder, 'is32Bit', false); 66 | 67 | $bytes = $encoder->encodeSFixed64(-123456789123456789); 68 | $expected = [ 69 | 1 => 41195, 70 | 2 => 21295, 71 | 3 => 25780, 72 | 4 => 65097 73 | ]; 74 | 75 | $this->assertEquals($expected, unpack('v*', $bytes)); 76 | } 77 | } -------------------------------------------------------------------------------- /tests/FieldTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($expected, Field::getPhpType($type)); 42 | } 43 | 44 | public function labelNameProvider() 45 | { 46 | return [ 47 | [-1, null], 48 | [Field::LABEL_OPTIONAL, 'optional'], 49 | [Field::LABEL_REQUIRED, 'required'], 50 | [Field::LABEL_REPEATED, 'repeated'] 51 | ]; 52 | } 53 | 54 | /** 55 | * @dataProvider labelNameProvider 56 | */ 57 | public function testGetLabelName($type, $expected) 58 | { 59 | $this->assertEquals($expected, Field::getLabelName($type)); 60 | } 61 | 62 | public function typeNameProvider() 63 | { 64 | return [ 65 | [-1, null], 66 | [Field::TYPE_DOUBLE, 'double'], 67 | [Field::TYPE_FLOAT, 'float'], 68 | [Field::TYPE_INT64, 'int64'], 69 | [Field::TYPE_UINT64, 'uint64'], 70 | [Field::TYPE_INT32, 'int32'], 71 | [Field::TYPE_FIXED64, 'fixed64'], 72 | [Field::TYPE_FIXED32, 'fixed32'], 73 | [Field::TYPE_BOOL, 'bool'], 74 | [Field::TYPE_STRING, 'string'], 75 | [Field::TYPE_MESSAGE, 'message'], 76 | [Field::TYPE_BYTES, 'bytes'], 77 | [Field::TYPE_UINT32, 'uint32'], 78 | [Field::TYPE_ENUM, 'enum'], 79 | [Field::TYPE_SFIXED32, 'sfixed32'], 80 | [Field::TYPE_SFIXED64, 'sfixed64'], 81 | [Field::TYPE_SINT32, 'sint32'], 82 | [Field::TYPE_SINT64, 'sint64'], 83 | ]; 84 | } 85 | 86 | /** 87 | * @dataProvider typeNameProvider 88 | */ 89 | public function testGetTypeName($type, $expected) 90 | { 91 | $this->assertEquals($expected, Field::getTypeName($type)); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Extension/ExtensionField.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class ExtensionField 15 | { 16 | /** 17 | * @var callback 18 | */ 19 | private $sizeCalculator; 20 | 21 | /** 22 | * @var callback 23 | */ 24 | private $writer; 25 | 26 | /** 27 | * @var callback 28 | */ 29 | private $reader; 30 | 31 | /** 32 | * @var string 33 | */ 34 | private $extendee; 35 | 36 | /** 37 | * @var string 38 | */ 39 | private $method; 40 | 41 | /** 42 | * @var string 43 | */ 44 | private $name; 45 | 46 | /** 47 | * @var integer 48 | */ 49 | private $tag; 50 | 51 | /** 52 | * @param string $extendee 53 | * @param string $name 54 | * @param integer $tag 55 | * @param callback $reader 56 | * @param callback $writer 57 | * @param callback $sizeCalculator 58 | * @param string $method 59 | */ 60 | public function __construct($extendee, $name, $tag, $reader, $writer, $sizeCalculator, $method = null) 61 | { 62 | $this->tag = $tag; 63 | $this->name = $name; 64 | $this->reader = $reader; 65 | $this->writer = $writer; 66 | $this->method = $method; 67 | $this->extendee = $extendee; 68 | $this->sizeCalculator = $sizeCalculator; 69 | } 70 | 71 | /** 72 | * @return string 73 | */ 74 | public function getExtendee() 75 | { 76 | return $this->extendee; 77 | } 78 | 79 | /** 80 | * @return string 81 | */ 82 | public function getMethod() 83 | { 84 | return $this->method; 85 | } 86 | 87 | /** 88 | * @return string 89 | */ 90 | public function getName() 91 | { 92 | return $this->name; 93 | } 94 | 95 | /** 96 | * @return integer 97 | */ 98 | public function getTag() 99 | { 100 | return $this->tag; 101 | } 102 | 103 | /** 104 | * @param \Protobuf\ComputeSizeContext $context 105 | * @param mixed $value 106 | * 107 | * @return integer 108 | */ 109 | public function serializedSize(ComputeSizeContext $context, $value) 110 | { 111 | return call_user_func($this->sizeCalculator, $context, $value); 112 | } 113 | 114 | /** 115 | * @param \Protobuf\WriteContext $context 116 | * @param mixed $value 117 | */ 118 | public function writeTo(WriteContext $context, $value) 119 | { 120 | call_user_func($this->writer, $context, $value); 121 | } 122 | 123 | /** 124 | * @param \Protobuf\ReadContext $context 125 | * @param integer $wire 126 | * 127 | * @return mixed 128 | */ 129 | public function readFrom(ReadContext $context, $wire) 130 | { 131 | return call_user_func($this->reader, $context, $wire); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/Extension/ExtensionFieldMap.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class ExtensionFieldMap extends SplObjectStorage implements Collection 20 | { 21 | /** 22 | * @var string 23 | */ 24 | protected $extendee; 25 | 26 | /** 27 | * @param string $extendee 28 | */ 29 | public function __construct($extendee = null) 30 | { 31 | $this->extendee = trim($extendee, '\\'); 32 | } 33 | 34 | /** 35 | * @param \Protobuf\Extension\ExtensionField $extension 36 | * @param mixed $value 37 | */ 38 | public function add(ExtensionField $extension, $value) 39 | { 40 | if ( ! $value instanceof Message) { 41 | $this->put($extension, $value); 42 | 43 | return; 44 | } 45 | 46 | $className = get_class($value); 47 | $existing = isset($this[$extension]) 48 | ? $this[$extension] 49 | : null; 50 | 51 | if ($existing instanceof $className) { 52 | $value->merge($existing); 53 | } 54 | 55 | $this->put($extension, $value); 56 | } 57 | 58 | /** 59 | * @param \Protobuf\Extension\ExtensionField $extension 60 | * @param mixed $value 61 | */ 62 | public function put(ExtensionField $extension, $value) 63 | { 64 | $extendee = trim($extension->getExtendee(), '\\'); 65 | 66 | if ($extendee !== $this->extendee) { 67 | throw new InvalidArgumentException(sprintf( 68 | 'Invalid extendee, %s is expected but %s given', 69 | $this->extendee, 70 | $extendee 71 | )); 72 | } 73 | 74 | $this->attach($extension, $value); 75 | } 76 | 77 | /** 78 | * @param \Protobuf\Extension\ExtensionField $key 79 | * 80 | * @return mixed 81 | */ 82 | public function get(ExtensionField $key) 83 | { 84 | return $this->offsetGet($key); 85 | } 86 | 87 | /** 88 | * @param \Protobuf\ComputeSizeContext $context 89 | * 90 | * @return integer 91 | */ 92 | public function serializedSize(ComputeSizeContext $context) 93 | { 94 | $size = 0; 95 | 96 | for ($this->rewind(); $this->valid(); $this->next()) { 97 | $extension = $this->current(); 98 | $value = $this->getInfo(); 99 | $size += $extension->serializedSize($context, $value); 100 | } 101 | 102 | return $size; 103 | } 104 | 105 | /** 106 | * @param \Protobuf\WriteContext $context 107 | */ 108 | public function writeTo(WriteContext $context) 109 | { 110 | for ($this->rewind(); $this->valid(); $this->next()) { 111 | $extension = $this->current(); 112 | $value = $this->getInfo(); 113 | 114 | $extension->writeTo($context, $value); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /tests/Binary/StreamWriterTest.php: -------------------------------------------------------------------------------- 1 | config); 16 | $binary = $this->getProtoContent('simple.bin'); 17 | 18 | $writer->writeVarint($stream, WireFormat::getFieldKey(1, WireFormat::WIRE_FIXED64)); 19 | $writer->writeDouble($stream, 123456789.12345); 20 | 21 | $writer->writeVarint($stream, WireFormat::getFieldKey(2, WireFormat::WIRE_FIXED32)); 22 | $writer->writeFloat($stream, 12345.123046875); 23 | 24 | $writer->writeVarint($stream, WireFormat::getFieldKey(3, WireFormat::WIRE_VARINT)); 25 | $writer->writeVarint($stream, -123456789123456789); 26 | 27 | $writer->writeVarint($stream, WireFormat::getFieldKey(4, WireFormat::WIRE_VARINT)); 28 | $writer->writeVarint($stream, 123456789123456789); 29 | 30 | $writer->writeVarint($stream, WireFormat::getFieldKey(5, WireFormat::WIRE_VARINT)); 31 | $writer->writeVarint($stream, -123456789); 32 | 33 | $writer->writeVarint($stream, WireFormat::getFieldKey(6, WireFormat::WIRE_FIXED64)); 34 | $writer->writeFixed64($stream, 123456789123456789); 35 | 36 | $writer->writeVarint($stream, WireFormat::getFieldKey(7, WireFormat::WIRE_FIXED32)); 37 | $writer->writeFixed32($stream, 123456789); 38 | 39 | $writer->writeVarint($stream, WireFormat::getFieldKey(8, WireFormat::WIRE_VARINT)); 40 | $writer->writeVarint($stream, 1); 41 | 42 | $writer->writeVarint($stream, WireFormat::getFieldKey(9, WireFormat::WIRE_LENGTH)); 43 | $writer->writeString($stream, 'foo'); 44 | 45 | $writer->writeVarint($stream, WireFormat::getFieldKey(12, WireFormat::WIRE_LENGTH)); 46 | $writer->writeByteStream($stream, Stream::wrap('bar')); 47 | 48 | $writer->writeVarint($stream, WireFormat::getFieldKey(13, WireFormat::WIRE_VARINT)); 49 | $writer->writeVarint($stream, 123456789); 50 | 51 | $writer->writeVarint($stream, WireFormat::getFieldKey(15, WireFormat::WIRE_FIXED32)); 52 | $writer->writeSFixed32($stream, -123456789); 53 | 54 | $writer->writeVarint($stream, WireFormat::getFieldKey(16, WireFormat::WIRE_FIXED64)); 55 | $writer->writeSFixed64($stream, -123456789123456789); 56 | 57 | $writer->writeVarint($stream, WireFormat::getFieldKey(17, WireFormat::WIRE_VARINT)); 58 | $writer->writeZigzag($stream, -123456789, 32); 59 | 60 | $writer->writeVarint($stream, WireFormat::getFieldKey(18, WireFormat::WIRE_VARINT)); 61 | $writer->writeZigzag($stream, -123456789123456789, 64); 62 | 63 | $this->assertEquals($binary, (string)$stream); 64 | } 65 | 66 | public function testWriteStream() 67 | { 68 | $source = Stream::create(); 69 | $target = Stream::create(); 70 | $writer = new StreamWriter($this->config); 71 | 72 | $writer->writeVarint($source, WireFormat::getFieldKey(1, WireFormat::WIRE_FIXED64)); 73 | $writer->writeDouble($source, 123456789.12345); 74 | 75 | $source->seek(0); 76 | $writer->writeStream($target, $source); 77 | 78 | $this->assertEquals((string) $source, (string) $target); 79 | } 80 | } -------------------------------------------------------------------------------- /tests/MessageSerializerTest.php: -------------------------------------------------------------------------------- 1 | getMock(Message::CLASS); 17 | $config = new Configuration(); 18 | $serializer = new MessageSerializer($config); 19 | $stream = Stream::create(); 20 | 21 | $message->expects($this->once()) 22 | ->method('toStream') 23 | ->willReturn($stream) 24 | ->with($this->equalTo($config)); 25 | 26 | $this->assertInstanceOf('Protobuf\Serializer', $serializer); 27 | $this->assertSame($stream, $serializer->serialize($message)); 28 | } 29 | 30 | public function testUnserializeMessage() 31 | { 32 | $class = FooStub_MessageSerializerTest::CLASS; 33 | $config = new Configuration(); 34 | $serializer = new MessageSerializer($config); 35 | $stream = Stream::create(); 36 | 37 | FooStub_MessageSerializerTest::$calls = []; 38 | 39 | $this->assertInstanceOf($class, $serializer->unserialize($class, $stream)); 40 | 41 | $this->assertCount(1, FooStub_MessageSerializerTest::$calls); 42 | $this->assertSame($stream, FooStub_MessageSerializerTest::$calls[0][0]); 43 | $this->assertSame($config, FooStub_MessageSerializerTest::$calls[0][1]); 44 | } 45 | 46 | public function testGetConfiguration() 47 | { 48 | $config1 = new Configuration(); 49 | $config2 = Configuration::getInstance(); 50 | 51 | $serializer1 = new MessageSerializer($config1); 52 | $serializer2 = new MessageSerializer(); 53 | 54 | $this->assertSame($config1, $serializer1->getConfiguration()); 55 | $this->assertSame($config2, $serializer2->getConfiguration()); 56 | } 57 | } 58 | 59 | class FooStub_MessageSerializerTest extends \Protobuf\AbstractMessage 60 | { 61 | public static $calls = []; 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | public function __construct($stream = null, \Protobuf\Configuration $configuration = null) 67 | { 68 | self::$calls[] = func_get_args(); 69 | } 70 | 71 | public static function fromStream($stream, \Protobuf\Configuration $configuration = null) 72 | { 73 | throw new \BadMethodCallException(__METHOD__); 74 | } 75 | 76 | public function extensions() 77 | { 78 | throw new \BadMethodCallException(__METHOD__); 79 | } 80 | 81 | public function unknownFieldSet() 82 | { 83 | throw new \BadMethodCallException(__METHOD__); 84 | } 85 | 86 | public function toStream(\Protobuf\Configuration $configuration = null) 87 | { 88 | throw new \BadMethodCallException(__METHOD__); 89 | } 90 | 91 | public function writeTo(\Protobuf\WriteContext $context) 92 | { 93 | throw new \BadMethodCallException(__METHOD__); 94 | } 95 | 96 | public function readFrom(\Protobuf\ReadContext $context) 97 | { 98 | throw new \BadMethodCallException(__METHOD__); 99 | } 100 | 101 | public function serializedSize(\Protobuf\ComputeSizeContext $context) 102 | { 103 | throw new \BadMethodCallException(__METHOD__); 104 | } 105 | 106 | public function merge(\Protobuf\Message $message) 107 | { 108 | throw new \BadMethodCallException(__METHOD__); 109 | } 110 | 111 | public function clear() 112 | { 113 | throw new \BadMethodCallException(__METHOD__); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/TextFormat.php: -------------------------------------------------------------------------------- 1 | 16 | * @author Fabio B. Silva 17 | */ 18 | class TextFormat 19 | { 20 | /** 21 | * @param \Protobuf\Message $message 22 | * @param integer $level 23 | * 24 | * @return \Protobuf\Stream 25 | */ 26 | public function encodeMessage(Message $message, $level = 0) 27 | { 28 | $reflect = new ReflectionClass($message); 29 | $properties = $reflect->getProperties(ReflectionProperty::IS_PROTECTED); 30 | $indent = str_repeat(' ', $level); 31 | $stream = Stream::create(); 32 | 33 | foreach ($properties as $property) { 34 | 35 | $property->setAccessible(true); 36 | 37 | $name = $property->getName(); 38 | $value = $property->getValue($message); 39 | 40 | if ($value === null) { 41 | continue; 42 | } 43 | 44 | if ( ! is_array($value) && ! ($value instanceof Traversable)) { 45 | 46 | if ( ! $value instanceof Message) { 47 | $item = $this->encodeValue($value); 48 | $buffer = $indent . $name . ': ' . $item . PHP_EOL; 49 | 50 | $stream->write($buffer, strlen($buffer)); 51 | 52 | continue; 53 | } 54 | 55 | $innerStream = $this->encodeMessage($value, $level + 1); 56 | $beginMessage = $indent . $name . ' {' . PHP_EOL; 57 | $endMessage = $indent . '}' . PHP_EOL; 58 | 59 | $stream->write($beginMessage, strlen($beginMessage)); 60 | $stream->writeStream($innerStream, $innerStream->getSize()); 61 | $stream->write($endMessage, strlen($endMessage)); 62 | 63 | continue; 64 | } 65 | 66 | foreach ($value as $val) { 67 | // Skip nullified repeated values 68 | if ($val == null) { 69 | continue; 70 | } 71 | 72 | if ( ! $val instanceof Message) { 73 | $item = $this->encodeValue($val); 74 | $buffer = $indent . $name . ': ' . $item . PHP_EOL; 75 | 76 | $stream->write($buffer, strlen($buffer)); 77 | 78 | continue; 79 | } 80 | 81 | $innerStream = $this->encodeMessage($val, $level + 1); 82 | $beginMessage = $indent . $name . ' {' . PHP_EOL; 83 | $endMessage = $indent . '}' . PHP_EOL; 84 | 85 | $stream->write($beginMessage, strlen($beginMessage)); 86 | $stream->writeStream($innerStream, $innerStream->getSize()); 87 | $stream->write($endMessage, strlen($endMessage)); 88 | } 89 | } 90 | 91 | $stream->seek(0); 92 | 93 | return $stream; 94 | } 95 | 96 | /** 97 | * @param scalar|array $value 98 | * 99 | * @return string 100 | */ 101 | public function encodeValue($value) 102 | { 103 | if (is_bool($value)) { 104 | return (int) $value; 105 | } 106 | 107 | if ($value instanceof Enum) { 108 | return $value->name(); 109 | } 110 | 111 | if ($value instanceof Stream) { 112 | return json_encode($value->__toString()); 113 | } 114 | 115 | return json_encode($value); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/WireFormat.php: -------------------------------------------------------------------------------- 1 | 12 | * @author Fabio B. Silva 13 | */ 14 | class WireFormat 15 | { 16 | const WIRE_VARINT = 0; 17 | const WIRE_FIXED64 = 1; 18 | const WIRE_LENGTH = 2; 19 | const WIRE_GROUP_START = 3; 20 | const WIRE_GROUP_END = 4; 21 | const WIRE_FIXED32 = 5; 22 | const WIRE_UNKNOWN = -1; 23 | 24 | const TAG_TYPE_BITS = 3; 25 | const TAG_TYPE_MASK = 0x7; 26 | 27 | /** 28 | * @var array 29 | */ 30 | private static $wireTypeMap = [ 31 | Field::TYPE_INT32 => WireFormat::WIRE_VARINT, 32 | Field::TYPE_INT64 => WireFormat::WIRE_VARINT, 33 | Field::TYPE_UINT32 => WireFormat::WIRE_VARINT, 34 | Field::TYPE_UINT64 => WireFormat::WIRE_VARINT, 35 | Field::TYPE_SINT32 => WireFormat::WIRE_VARINT, 36 | Field::TYPE_SINT64 => WireFormat::WIRE_VARINT, 37 | Field::TYPE_BOOL => WireFormat::WIRE_VARINT, 38 | Field::TYPE_ENUM => WireFormat::WIRE_VARINT, 39 | Field::TYPE_FIXED64 => WireFormat::WIRE_FIXED64, 40 | Field::TYPE_SFIXED64 => WireFormat::WIRE_FIXED64, 41 | Field::TYPE_DOUBLE => WireFormat::WIRE_FIXED64, 42 | Field::TYPE_STRING => WireFormat::WIRE_LENGTH, 43 | Field::TYPE_BYTES => WireFormat::WIRE_LENGTH, 44 | Field::TYPE_MESSAGE => WireFormat::WIRE_LENGTH, 45 | Field::TYPE_FIXED32 => WireFormat::WIRE_FIXED32, 46 | Field::TYPE_SFIXED32 => WireFormat::WIRE_FIXED32, 47 | Field::TYPE_FLOAT => WireFormat::WIRE_FIXED32, 48 | ]; 49 | 50 | /** 51 | * Given a field type, determines the wire type. 52 | * 53 | * @param integer $type 54 | * @param integer $default 55 | * 56 | * @return integer 57 | */ 58 | public static function getWireType($type, $default) 59 | { 60 | // Unknown types just return the reported wire type 61 | return isset(self::$wireTypeMap[$type]) 62 | ? self::$wireTypeMap[$type] 63 | : $default; 64 | } 65 | 66 | /** 67 | * Assert the wire type match 68 | * 69 | * @param integer $wire 70 | * @param integer $type 71 | */ 72 | public static function assertWireType($wire, $type) 73 | { 74 | $expected = WireFormat::getWireType($type, $wire); 75 | 76 | if ($wire !== $expected) { 77 | throw new RuntimeException(sprintf( 78 | "Expected wire type %s but got %s for type %s.", 79 | $expected, 80 | $wire, 81 | $type 82 | )); 83 | } 84 | } 85 | 86 | /** 87 | * Given a tag value, determines the field number (the upper 29 bits). 88 | * 89 | * @param integer $tag 90 | * 91 | * @return integer 92 | */ 93 | public static function getTagFieldNumber($tag) 94 | { 95 | return $tag >> self::TAG_TYPE_BITS; 96 | } 97 | 98 | /** 99 | * Given a tag value, determines the wire type (the lower 3 bits). 100 | * 101 | * @param integer $tag 102 | * 103 | * @return integer 104 | */ 105 | public static function getTagWireType($tag) 106 | { 107 | return $tag & self::TAG_TYPE_MASK; 108 | } 109 | 110 | /** 111 | * Makes a tag value given a field number and wire type 112 | * 113 | * @param integer $tag 114 | * @param integer $wireType 115 | * 116 | * @return integer 117 | */ 118 | public static function getFieldKey($tag, $wireType) 119 | { 120 | return ($tag << self::TAG_TYPE_BITS) | $wireType; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/Field.php: -------------------------------------------------------------------------------- 1 | 9 | * @author Fabio B. Silva 10 | */ 11 | class Field 12 | { 13 | const LABEL_OPTIONAL = 1; 14 | const LABEL_REQUIRED = 2; 15 | const LABEL_REPEATED = 3; 16 | const LABEL_UNKNOWN = -1; 17 | 18 | const TYPE_DOUBLE = 1; 19 | const TYPE_FLOAT = 2; 20 | const TYPE_INT64 = 3; 21 | const TYPE_UINT64 = 4; 22 | const TYPE_INT32 = 5; 23 | const TYPE_FIXED64 = 6; 24 | const TYPE_FIXED32 = 7; 25 | const TYPE_BOOL = 8; 26 | const TYPE_STRING = 9; 27 | const TYPE_GROUP = 10; 28 | const TYPE_MESSAGE = 11; 29 | const TYPE_BYTES = 12; 30 | const TYPE_UINT32 = 13; 31 | const TYPE_ENUM = 14; 32 | const TYPE_SFIXED32 = 15; 33 | const TYPE_SFIXED64 = 16; 34 | const TYPE_SINT32 = 17; 35 | const TYPE_SINT64 = 18; 36 | const TYPE_UNKNOWN = -1; 37 | 38 | /** 39 | * @var array 40 | */ 41 | protected static $names = [ 42 | self::TYPE_DOUBLE => 'double', 43 | self::TYPE_FLOAT => 'float', 44 | self::TYPE_INT64 => 'int64', 45 | self::TYPE_UINT64 => 'uint64', 46 | self::TYPE_INT32 => 'int32', 47 | self::TYPE_FIXED64 => 'fixed64', 48 | self::TYPE_FIXED32 => 'fixed32', 49 | self::TYPE_BOOL => 'bool', 50 | self::TYPE_STRING => 'string', 51 | self::TYPE_MESSAGE => 'message', 52 | self::TYPE_BYTES => 'bytes', 53 | self::TYPE_UINT32 => 'uint32', 54 | self::TYPE_ENUM => 'enum', 55 | self::TYPE_SFIXED32 => 'sfixed32', 56 | self::TYPE_SFIXED64 => 'sfixed64', 57 | self::TYPE_SINT32 => 'sint32', 58 | self::TYPE_SINT64 => 'sint64', 59 | ]; 60 | 61 | /** 62 | * Obtain the label name (repeated, optional, required). 63 | * 64 | * @param string $label 65 | * 66 | * @return string 67 | */ 68 | public static function getLabelName($label) 69 | { 70 | if ($label === self::LABEL_OPTIONAL) { 71 | return 'optional'; 72 | } 73 | 74 | if ($label === self::LABEL_REQUIRED) { 75 | return 'required'; 76 | } 77 | 78 | if ($label === self::LABEL_REPEATED) { 79 | return 'repeated'; 80 | } 81 | 82 | return null; 83 | } 84 | 85 | /** 86 | * @param integer $type 87 | * 88 | * @return string 89 | */ 90 | public static function getTypeName($type) 91 | { 92 | return isset(self::$names[$type]) 93 | ? self::$names[$type] 94 | : null; 95 | } 96 | 97 | /** 98 | * @param integer $type 99 | * 100 | * @return string 101 | */ 102 | public static function getPhpType($type) 103 | { 104 | switch ($type) { 105 | case self::TYPE_DOUBLE: 106 | case self::TYPE_FLOAT: 107 | return 'float'; 108 | case self::TYPE_INT64: 109 | case self::TYPE_UINT64: 110 | case self::TYPE_INT32: 111 | case self::TYPE_FIXED64: 112 | case self::TYPE_FIXED32: 113 | case self::TYPE_UINT32: 114 | case self::TYPE_SFIXED32: 115 | case self::TYPE_SFIXED64: 116 | case self::TYPE_SINT32: 117 | case self::TYPE_SINT64: 118 | return 'int'; 119 | case self::TYPE_BOOL: 120 | return 'bool'; 121 | case self::TYPE_STRING: 122 | return 'string'; 123 | case self::TYPE_BYTES: 124 | return '\Protobuf\Stream'; 125 | default: 126 | return null; 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /tests/Binary/StreamReaderTest.php: -------------------------------------------------------------------------------- 1 | readVarint($stream); 15 | $tag = WireFormat::getTagFieldNumber($key); 16 | $wire = WireFormat::getTagWireType($key); 17 | 18 | $this->assertEquals($expectedTag, $tag); 19 | $this->assertEquals($expectedWire, $wire); 20 | } 21 | 22 | public function testReadSimpleMessage() 23 | { 24 | $stream = Stream::wrap($this->getProtoContent('simple.bin')); 25 | $reader = new StreamReader($this->config); 26 | 27 | $this->assertNextTagWire($reader, $stream, 1, WireFormat::WIRE_FIXED64); 28 | $this->assertEquals(123456789.12345, $reader->readDouble($stream)); 29 | 30 | $this->assertNextTagWire($reader, $stream, 2, WireFormat::WIRE_FIXED32); 31 | $this->assertEquals(12345.123046875, $reader->readFloat($stream)); 32 | 33 | $this->assertNextTagWire($reader, $stream, 3, WireFormat::WIRE_VARINT); 34 | $this->assertEquals(-123456789123456789, $reader->readVarint($stream)); 35 | 36 | $this->assertNextTagWire($reader, $stream, 4, WireFormat::WIRE_VARINT); 37 | $this->assertEquals(123456789123456789, $reader->readVarint($stream)); 38 | 39 | $this->assertNextTagWire($reader, $stream, 5, WireFormat::WIRE_VARINT); 40 | $this->assertEquals(-123456789, $reader->readVarint($stream)); 41 | 42 | $this->assertNextTagWire($reader, $stream, 6, WireFormat::WIRE_FIXED64); 43 | $this->assertEquals(123456789123456789, $reader->readFixed64($stream)); 44 | 45 | $this->assertNextTagWire($reader, $stream, 7, WireFormat::WIRE_FIXED32); 46 | $this->assertEquals(123456789, $reader->readFixed32($stream)); 47 | 48 | $this->assertNextTagWire($reader, $stream, 8, WireFormat::WIRE_VARINT); 49 | $this->assertEquals(true, $reader->readBool($stream)); 50 | 51 | $this->assertNextTagWire($reader, $stream, 9, WireFormat::WIRE_LENGTH); 52 | $this->assertEquals('foo', $reader->readString($stream)); 53 | 54 | $this->assertNextTagWire($reader, $stream, 12, WireFormat::WIRE_LENGTH); 55 | $this->assertInstanceOf('Protobuf\Stream', ($byteStream = $reader->readByteStream($stream))); 56 | $this->assertEquals('bar', (string) $byteStream); 57 | 58 | $this->assertNextTagWire($reader, $stream, 13, WireFormat::WIRE_VARINT); 59 | $this->assertEquals(123456789, $reader->readVarint($stream)); 60 | 61 | $this->assertNextTagWire($reader, $stream, 15, WireFormat::WIRE_FIXED32); 62 | $this->assertEquals(-123456789, $reader->readSFixed32($stream)); 63 | 64 | $this->assertNextTagWire($reader, $stream, 16, WireFormat::WIRE_FIXED64); 65 | $this->assertEquals(-123456789123456789, $reader->readSFixed64($stream)); 66 | 67 | $this->assertNextTagWire($reader, $stream, 17, WireFormat::WIRE_VARINT); 68 | $this->assertEquals(-123456789, $reader->readZigzag($stream)); 69 | 70 | $this->assertNextTagWire($reader, $stream, 18, WireFormat::WIRE_VARINT); 71 | $this->assertEquals(-123456789123456789, $reader->readZigzag($stream)); 72 | } 73 | 74 | /** 75 | * @expectedException RuntimeException 76 | * @expectedExceptionMessage Groups are deprecated in Protocol Buffers and unsupported. 77 | */ 78 | public function testReadUnknownWireFormatGroupException() 79 | { 80 | $stream = Stream::create($this->getProtoContent('simple.bin')); 81 | $reader = new StreamReader($this->config); 82 | 83 | $reader->readUnknown($stream, WireFormat::WIRE_GROUP_START); 84 | } 85 | 86 | /** 87 | * @expectedException RuntimeException 88 | * @expectedExceptionMessage Unsupported wire type '-1' while reading unknown field. 89 | */ 90 | public function testReadUnknownWireFormatException() 91 | { 92 | $stream = Stream::create($this->getProtoContent('simple.bin')); 93 | $reader = new StreamReader($this->config); 94 | 95 | $reader->readUnknown($stream, -1); 96 | } 97 | } -------------------------------------------------------------------------------- /src/Configuration.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class Configuration 17 | { 18 | /** 19 | * @var \Protobuf\Extension\ExtensionRegistry 20 | */ 21 | private $extensionRegistry; 22 | 23 | /** 24 | * @var \Protobuf\Binary\Platform\PlatformFactory 25 | */ 26 | private $platformFactory; 27 | 28 | /** 29 | * @var \Protobuf\Binary\StreamWriter 30 | */ 31 | private $streamWriter; 32 | 33 | /** 34 | * @var \Protobuf\Binary\StreamReader 35 | */ 36 | private $streamReader; 37 | 38 | /** 39 | * @var \Protobuf\Binary\SizeCalculator 40 | */ 41 | private $sizeCalculator; 42 | 43 | /** 44 | * @var \Protobuf\DescriptorLoader 45 | */ 46 | protected static $instance; 47 | 48 | /** 49 | * Return a ExtensionRegistry. 50 | * 51 | * @return \Protobuf\Extension\ExtensionRegistry 52 | */ 53 | public function getExtensionRegistry() 54 | { 55 | if ($this->extensionRegistry === null) { 56 | $this->extensionRegistry = new ExtensionRegistry(); 57 | } 58 | 59 | return $this->extensionRegistry; 60 | } 61 | 62 | /** 63 | * Set a ExtensionRegistry. 64 | * 65 | * @param \Protobuf\Extension\ExtensionRegistry $extensionRegistry 66 | */ 67 | public function setExtensionRegistry(ExtensionRegistry $extensionRegistry) 68 | { 69 | $this->extensionRegistry = $extensionRegistry; 70 | } 71 | 72 | /** 73 | * Return a PlatformFactory. 74 | * 75 | * @return \Protobuf\Binary\Platform\PlatformFactory 76 | */ 77 | public function getPlatformFactory() 78 | { 79 | if ($this->platformFactory !== null) { 80 | return $this->platformFactory; 81 | } 82 | 83 | return $this->platformFactory = new PlatformFactory(); 84 | } 85 | 86 | /** 87 | * Return a StreamReader 88 | * 89 | * @return \Protobuf\Binary\StreamReader 90 | */ 91 | public function getStreamReader() 92 | { 93 | if ($this->streamReader !== null) { 94 | return $this->streamReader; 95 | } 96 | 97 | return $this->streamReader = new StreamReader($this); 98 | } 99 | 100 | /** 101 | * Return a StreamWriter 102 | * 103 | * @return \Protobuf\Binary\StreamWriter 104 | */ 105 | public function getStreamWriter() 106 | { 107 | if ($this->streamWriter !== null) { 108 | return $this->streamWriter; 109 | } 110 | 111 | return $this->streamWriter = new StreamWriter($this); 112 | } 113 | 114 | /** 115 | * Return a SizeCalculator 116 | * 117 | * @return \Protobuf\Binary\SizeCalculator 118 | */ 119 | public function getSizeCalculator() 120 | { 121 | if ($this->sizeCalculator !== null) { 122 | return $this->sizeCalculator; 123 | } 124 | 125 | return $this->sizeCalculator = new SizeCalculator($this); 126 | } 127 | 128 | /** 129 | * Sets the PlatformFactory. 130 | * 131 | * @param \Protobuf\Binary\Platform\PlatformFactory $platformFactory 132 | */ 133 | public function setPlatformFactory(PlatformFactory $platformFactory) 134 | { 135 | $this->platformFactory = $platformFactory; 136 | } 137 | 138 | /** 139 | * Create a compute size context. 140 | * 141 | * @return \Protobuf\ComputeSizeContext 142 | */ 143 | public function createComputeSizeContext() 144 | { 145 | $calculator = $this->getSizeCalculator(); 146 | $context = new ComputeSizeContext($calculator); 147 | 148 | return $context; 149 | } 150 | 151 | /** 152 | * Create a write context. 153 | * 154 | * @return \Protobuf\WriteContext 155 | */ 156 | public function createWriteContext() 157 | { 158 | $stream = Stream::create(); 159 | $writer = $this->getStreamWriter(); 160 | $sizeContext = $this->createComputeSizeContext(); 161 | $context = new WriteContext($stream, $writer, $sizeContext); 162 | 163 | return $context; 164 | } 165 | 166 | /** 167 | * Create a read context. 168 | * 169 | * @param \Protobuf\Stream|resource|string $stream 170 | * 171 | * @return \Protobuf\ReadContext 172 | */ 173 | public function createReadContext($stream) 174 | { 175 | $reader = $this->getStreamReader(); 176 | $registry = $this->extensionRegistry; 177 | $context = new ReadContext($stream, $reader, $registry); 178 | 179 | return $context; 180 | } 181 | 182 | /** 183 | * Returns single instance of this class 184 | * 185 | * @return \Protobuf\Configuration 186 | */ 187 | public static function getInstance() 188 | { 189 | if (self::$instance !== null) { 190 | return self::$instance; 191 | } 192 | 193 | return self::$instance = new Configuration(); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/Binary/SizeCalculator.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class SizeCalculator 18 | { 19 | /** 20 | * @var \Protobuf\Configuration 21 | */ 22 | protected $config; 23 | 24 | /** 25 | * Constructor 26 | * 27 | * @param \Protobuf\Configuration $config 28 | */ 29 | public function __construct(Configuration $config) 30 | { 31 | $this->config = $config; 32 | } 33 | 34 | /** 35 | * Compute the number of bytes that would be needed to encode a varint. 36 | * 37 | * @param integer $value 38 | * 39 | * @return integer 40 | */ 41 | public function computeVarintSize($value) 42 | { 43 | if (($value & (0xffffffff << 7)) === 0) { 44 | return 1; 45 | } 46 | 47 | if (($value & (0xffffffff << 14)) === 0) { 48 | return 2; 49 | } 50 | 51 | if (($value & (0xffffffff << 21)) === 0) { 52 | return 3; 53 | } 54 | 55 | if (($value & (0xffffffff << 28)) === 0) { 56 | return 4; 57 | } 58 | 59 | if (($value & (0xffffffff << 35)) === 0) { 60 | return 5; 61 | } 62 | 63 | if (($value & (0xffffffff << 42)) === 0) { 64 | return 6; 65 | } 66 | 67 | if (($value & (0xffffffff << 49)) === 0) { 68 | return 7; 69 | } 70 | 71 | if (($value & (0xffffffff << 56)) === 0) { 72 | return 8; 73 | } 74 | 75 | if (($value & (0xffffffff << 63)) === 0) { 76 | return 9; 77 | } 78 | 79 | return 10; 80 | } 81 | 82 | /** 83 | * Compute the number of bytes that would be needed to encode a zigzag 32. 84 | * 85 | * @param integer $value 86 | * 87 | * @return integer 88 | */ 89 | public function computeZigzag32Size($value) 90 | { 91 | $varint = ($value << 1) ^ ($value >> 32 - 1); 92 | $size = $this->computeVarintSize($varint); 93 | 94 | return $size; 95 | } 96 | 97 | /** 98 | * Compute the number of bytes that would be needed to encode a zigzag 64. 99 | * 100 | * @param integer $value 101 | * 102 | * @return integer 103 | */ 104 | public function computeZigzag64Size($value) 105 | { 106 | $varint = ($value << 1) ^ ($value >> 64 - 1); 107 | $size = $this->computeVarintSize($varint); 108 | 109 | return $size; 110 | } 111 | 112 | /** 113 | * Compute the number of bytes that would be needed to encode a string. 114 | * 115 | * @param integer $value 116 | * 117 | * @return integer 118 | */ 119 | public function computeStringSize($value) 120 | { 121 | $length = mb_strlen($value, '8bit'); 122 | $size = $length + $this->computeVarintSize($length); 123 | 124 | return $size; 125 | } 126 | 127 | /** 128 | * Compute the number of bytes that would be needed to encode a stream of bytes. 129 | * 130 | * @param \Protobuf\Stream $value 131 | * 132 | * @return integer 133 | */ 134 | public function computeByteStreamSize(Stream $value) 135 | { 136 | $length = $value->getSize(); 137 | $size = $length + $this->computeVarintSize($length); 138 | 139 | return $size; 140 | } 141 | 142 | /** 143 | * Compute the number of bytes that would be needed to encode a sFixed32. 144 | * 145 | * @return integer 146 | */ 147 | public function computeSFixed32Size() 148 | { 149 | return 4; 150 | } 151 | 152 | /** 153 | * Compute the number of bytes that would be needed to encode a fixed32. 154 | * 155 | * @return integer 156 | */ 157 | public function computeFixed32Size() 158 | { 159 | return 4; 160 | } 161 | 162 | /** 163 | * Compute the number of bytes that would be needed to encode a sFixed64. 164 | * 165 | * @return integer 166 | */ 167 | public function computeSFixed64Size() 168 | { 169 | return 8; 170 | } 171 | 172 | /** 173 | * Compute the number of bytes that would be needed to encode a fixed64. 174 | * 175 | * 176 | * @return integer 177 | */ 178 | public function computeFixed64Size() 179 | { 180 | return 8; 181 | } 182 | 183 | /** 184 | * Compute the number of bytes that would be needed to encode a float. 185 | * 186 | * @return integer 187 | */ 188 | public function computeFloatSize() 189 | { 190 | return 4; 191 | } 192 | 193 | /** 194 | * Compute the number of bytes that would be needed to encode a double. 195 | * 196 | * @return integer 197 | */ 198 | public function computeDoubleSize() 199 | { 200 | return 8; 201 | } 202 | 203 | /** 204 | * Compute the number of bytes that would be needed to encode a bool. 205 | * 206 | * @return integer 207 | */ 208 | public function computeBoolSize() 209 | { 210 | return 1; 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /tests/TextFormatTest.php: -------------------------------------------------------------------------------- 1 | textFormat = new TextFormat($this->config); 26 | } 27 | 28 | public function testFormatSimple() 29 | { 30 | $simple = new Simple(); 31 | 32 | $simple->setBool(true); 33 | $simple->setBytes("bar"); 34 | $simple->setString("foo"); 35 | $simple->setFloat(12345.123); 36 | $simple->setUint32(123456789); 37 | $simple->setInt32(-123456789); 38 | $simple->setFixed32(123456789); 39 | $simple->setSint32(-123456789); 40 | $simple->setSfixed32(-123456789); 41 | $simple->setDouble(123456789.12345); 42 | $simple->setInt64(-123456789123456789); 43 | $simple->setUint64(123456789123456789); 44 | $simple->setFixed64(123456789123456789); 45 | $simple->setSint64(-123456789123456789); 46 | $simple->setSfixed64(-123456789123456789); 47 | 48 | $expected = $this->getProtoContent('simple.txt'); 49 | $actual = $this->textFormat->encodeMessage($simple); 50 | 51 | $this->assertEquals($expected, (string) $actual); 52 | } 53 | 54 | public function testFormatRepeatedString() 55 | { 56 | $repeated = new Repeated(); 57 | 58 | $repeated->addString('one'); 59 | $repeated->addString('two'); 60 | $repeated->addString('three'); 61 | 62 | $expected = $this->getProtoContent('repeated-string.txt'); 63 | $actual = $this->textFormat->encodeMessage($repeated); 64 | 65 | $this->assertEquals($expected, (string) $actual); 66 | } 67 | 68 | public function testFormatRepeatedInt() 69 | { 70 | $repeated = new Repeated(); 71 | 72 | $repeated->addInt(1); 73 | $repeated->addInt(2); 74 | $repeated->addInt(3); 75 | 76 | $expected = $this->getProtoContent('repeated-int32.txt'); 77 | $actual = $this->textFormat->encodeMessage($repeated); 78 | 79 | $this->assertEquals($expected, (string) $actual); 80 | } 81 | 82 | public function testFormatRepeatedNested() 83 | { 84 | $repeated = new Repeated(); 85 | $nested1 = new Repeated\Nested(); 86 | $nested2 = new Repeated\Nested(); 87 | $nested3 = new Repeated\Nested(); 88 | 89 | $nested1->setId(1); 90 | $nested2->setId(2); 91 | $nested3->setId(3); 92 | 93 | $repeated->addNested($nested1); 94 | $repeated->addNested($nested2); 95 | $repeated->addNested($nested3); 96 | 97 | $expected = $this->getProtoContent('repeated-nested.txt'); 98 | $actual = $this->textFormat->encodeMessage($repeated); 99 | 100 | $this->assertEquals($expected, (string) $actual); 101 | } 102 | 103 | public function testFormatComplexMessage() 104 | { 105 | $book = new AddressBook(); 106 | $person = new Person(); 107 | 108 | $person->setId(2051); 109 | $person->setName('John Doe'); 110 | $person->setEmail('john.doe@gmail.com'); 111 | 112 | $phone = new Person\PhoneNumber(); 113 | 114 | $phone->setNumber('1231231212'); 115 | $phone->setType(Person\PhoneType::HOME()); 116 | 117 | $person->addPhone($phone); 118 | 119 | $phone = new Person\PhoneNumber(); 120 | 121 | $phone->setNumber('55512321312'); 122 | $phone->setType(Person\PhoneType::MOBILE()); 123 | 124 | $person->addPhone($phone); 125 | $book->addPerson($person); 126 | 127 | $person = new Person(); 128 | 129 | $person->setId(23); 130 | $person->setName('Iván Montes'); 131 | $person->setEmail('drslump@pollinimini.net'); 132 | 133 | $phone = new Person\PhoneNumber(); 134 | 135 | $phone->setNumber('3493123123'); 136 | $phone->setType(Person\PhoneType::WORK()); 137 | 138 | $person->addPhone($phone); 139 | $book->addPerson($person); 140 | 141 | $expected = $this->getProtoContent('addressbook.txt'); 142 | $actual = $this->textFormat->encodeMessage($book); 143 | 144 | $this->assertEquals($expected, (string) $actual); 145 | } 146 | 147 | public function testFormatTreeMessage() 148 | { 149 | $root = new Tree\Node(); 150 | $admin = new Tree\Node(); 151 | $fabio = new Tree\Node(); 152 | 153 | $root->setPath('/Users'); 154 | $fabio->setPath('/Users/fabio'); 155 | $admin->setPath('/Users/admin'); 156 | 157 | // avoid recursion 158 | $parent = clone $root; 159 | 160 | $admin->setParent($parent); 161 | $fabio->setParent($parent); 162 | 163 | $root->addChildren($fabio); 164 | $root->addChildren($admin); 165 | 166 | $expected = $this->getProtoContent('tree.txt'); 167 | $actual = $root->__toString(); 168 | 169 | $this->assertEquals($expected, (string) $actual); 170 | } 171 | 172 | public function testFormatTotring() 173 | { 174 | $repeated = new Repeated(); 175 | 176 | $repeated->addString('one'); 177 | $repeated->addString('two'); 178 | $repeated->addString('three'); 179 | 180 | $expected = $this->getProtoContent('repeated-string.txt'); 181 | $actual = $repeated->__toString(); 182 | 183 | $this->assertEquals($expected, (string) $actual); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /tests/StreamTest.php: -------------------------------------------------------------------------------- 1 | assertFalse(is_resource($handle)); 17 | } 18 | 19 | public function testConvertsToString() 20 | { 21 | $handle = fopen('php://temp', 'w+'); 22 | $stream = new Stream($handle); 23 | 24 | fwrite($handle, 'data'); 25 | 26 | $this->assertEquals('data', (string) $stream); 27 | $this->assertEquals('data', (string) $stream); 28 | } 29 | 30 | public function testGetsContents() 31 | { 32 | $handle = fopen('php://temp', 'w+'); 33 | $stream = new Stream($handle); 34 | 35 | fwrite($handle, 'data'); 36 | 37 | $this->assertEquals('data', $stream->getContents()); 38 | } 39 | 40 | public function testChecksEof() 41 | { 42 | $handle = fopen('php://temp', 'w+'); 43 | $stream = new Stream($handle); 44 | 45 | fwrite($handle, 'data'); 46 | 47 | $this->assertFalse($stream->eof()); 48 | $stream->read(4); 49 | $this->assertTrue($stream->eof()); 50 | } 51 | 52 | public function testGetSize() 53 | { 54 | $handle = fopen('php://temp', 'w+'); 55 | $stream = new Stream($handle); 56 | 57 | $this->assertEquals(3, fwrite($handle, 'foo')); 58 | $this->assertEquals(3, $stream->getSize()); 59 | $this->assertEquals(4, $stream->write('test', strlen('test'))); 60 | $this->assertEquals(7, $stream->getSize()); 61 | $this->assertEquals(7, $stream->getSize()); 62 | } 63 | 64 | public function testStreamPosition() 65 | { 66 | $handle = fopen('php://temp', 'w+'); 67 | $stream = new Stream($handle); 68 | 69 | $this->assertEquals(0, $stream->tell()); 70 | $stream->write('foo', strlen('foo')); 71 | $this->assertEquals(3, $stream->tell()); 72 | 73 | $stream->seek(1); 74 | 75 | $this->assertEquals(1, $stream->tell()); 76 | $this->assertSame(ftell($handle), $stream->tell()); 77 | } 78 | 79 | public function testWriteStream() 80 | { 81 | $source = Stream::create(); 82 | $target = Stream::create(); 83 | 84 | $source->write('foo', strlen('foo')); 85 | $source->seek(0); 86 | 87 | $target->writeStream($source, $source->getSize()); 88 | 89 | $this->assertEquals(3, $source->getSize()); 90 | $this->assertEquals(3, $target->getSize()); 91 | 92 | $this->assertEquals('foo', (string) $source); 93 | $this->assertEquals('foo', (string) $target); 94 | } 95 | 96 | public function testReadStream() 97 | { 98 | $source = Stream::wrap('FOObar'); 99 | $read1 = $source->readStream(3); 100 | $read2 = $source->readStream(3); 101 | 102 | $this->assertInstanceOf('Protobuf\Stream', $read1); 103 | $this->assertInstanceOf('Protobuf\Stream', $read2); 104 | 105 | $this->assertEquals(3, $read1->getSize()); 106 | $this->assertEquals(3, $read2->getSize()); 107 | 108 | $this->assertEquals('FOO', (string) $read1); 109 | $this->assertEquals('bar', (string) $read2); 110 | } 111 | 112 | public function testPositionOfResource() 113 | { 114 | $handle = fopen(__FILE__, 'r'); 115 | 116 | fseek($handle, 10); 117 | 118 | $stream = Stream::wrap($handle); 119 | 120 | $this->assertEquals(10, $stream->tell()); 121 | } 122 | 123 | public function testCreateStreamFromString() 124 | { 125 | $stream = Stream::wrap('foo'); 126 | 127 | $this->assertInstanceOf('Protobuf\Stream', $stream); 128 | $this->assertEquals('foo', $stream->getContents()); 129 | } 130 | 131 | public function testCreateStreamFromEmptyString() 132 | { 133 | $this->assertInstanceOf('Protobuf\Stream', Stream::wrap()); 134 | } 135 | 136 | public function testCreateStreamFromResource() 137 | { 138 | $handle = fopen(__FILE__, 'r'); 139 | $stream = Stream::wrap($handle); 140 | $content = file_get_contents(__FILE__); 141 | 142 | $this->assertInstanceOf('Protobuf\Stream', $stream); 143 | $this->assertSame($content, (string) $stream); 144 | } 145 | 146 | /** 147 | * @expectedException \RuntimeException 148 | * @expectedExceptionMessage Failed to write 2 bytes 149 | */ 150 | public function testWriteException() 151 | { 152 | $handle = fopen('php://temp', 'w+'); 153 | $stream = new Stream($handle); 154 | 155 | $stream->write('', 2); 156 | } 157 | 158 | /** 159 | * @expectedException \RuntimeException 160 | * @expectedExceptionMessage Unable to seek stream position to -1 161 | */ 162 | public function testSeekException() 163 | { 164 | $handle = fopen('php://temp', 'w+'); 165 | $stream = new Stream($handle); 166 | 167 | $stream->seek(-1); 168 | } 169 | 170 | /** 171 | * @expectedException \RuntimeException 172 | * @expectedExceptionMessage Failed to write stream with 3 bytes 173 | */ 174 | public function testWriteStreamException() 175 | { 176 | $source = Stream::create(); 177 | $target = Stream::create(); 178 | 179 | $target->writeStream($source, 3); 180 | } 181 | 182 | /** 183 | * @expectedException \InvalidArgumentException 184 | */ 185 | public function testThrowsExceptionForUnknown() 186 | { 187 | Stream::wrap(new \stdClass()); 188 | } 189 | 190 | /** 191 | * @expectedException \InvalidArgumentException 192 | */ 193 | public function testThrowsExceptionOnInvalidArgument() 194 | { 195 | new Stream(null); 196 | } 197 | 198 | public function testCanSetSize() 199 | { 200 | $this->assertEquals(10, Stream::wrap('', 10)->getSize()); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /tests/ProtocSerializeMessageTest.php: -------------------------------------------------------------------------------- 1 | tell(); 130 | $escaped = '"' . $value . '"'; 131 | 132 | $value->seek($tell); 133 | } 134 | 135 | $message->$setter($value); 136 | 137 | $encoded = $message->toStream(); 138 | $expected = $this->executeProtoc("$field: $escaped", $class, $proto); 139 | 140 | $this->assertEquals(bin2hex($expected), bin2hex($encoded), "Encoding $field with value $value"); 141 | } 142 | 143 | /** 144 | * @dataProvider simpleMessageProvider 145 | */ 146 | public function testDecodeSimpleMessageComparingTypesWithProtoc($field, $value) 147 | { 148 | $escaped = $value; 149 | $proto = 'simple'; 150 | $getter = 'get' . ucfirst($field); 151 | $class = 'ProtobufTest.Protos.Simple'; 152 | 153 | if (is_string($value)) { 154 | $escaped = '"' . $value . '"'; 155 | } 156 | 157 | if ($value instanceof \Protobuf\Stream) { 158 | $tell = $value->tell(); 159 | $escaped = '"' . $value . '"'; 160 | 161 | $value->seek($tell); 162 | } 163 | 164 | $binary = $this->executeProtoc("$field: $escaped", $class, $proto); 165 | $message = Simple::fromStream(Stream::wrap($binary)); 166 | $result = $message->$getter(); 167 | 168 | // Hack the comparison for float precision 169 | if (is_float($value)) { 170 | $precision = strlen($value) - strpos($value, '.'); 171 | $result = round($result, $precision); 172 | } 173 | 174 | if ($result instanceof \Protobuf\Stream) { 175 | $result = (string) $result; 176 | } 177 | 178 | $this->assertEquals($value, $result, "Decoding $field with value $value"); 179 | } 180 | 181 | public function testEncodeAndDecodeEnumComparingWithProtoc() 182 | { 183 | $proto = 'complex'; 184 | $complex = new Complex(); 185 | $value = Complex\Enum::FOO(); 186 | $class = 'ProtobufTest.Protos.Complex'; 187 | 188 | $complex->setEnum($value); 189 | 190 | $encoded = $complex->toStream(); 191 | $expected = $this->executeProtoc("enum: FOO", $class, $proto); 192 | $decoded = Complex::fromStream(Stream::wrap($expected)); 193 | 194 | $this->assertInstanceOf(Complex::CLASS, $decoded); 195 | $this->assertEquals(bin2hex($expected), bin2hex($encoded)); 196 | $this->assertEquals(Complex\Enum::FOO(), $decoded->getEnum()); 197 | } 198 | 199 | public function testEncodeAndDecodeNestedMessageComparingWithProtoc() 200 | { 201 | $proto = 'complex'; 202 | $complex = new Complex(); 203 | $nested = new Complex\Nested(); 204 | $input = 'nested { foo: "FOO" }'; 205 | $class = 'ProtobufTest.Protos.Complex'; 206 | 207 | $nested->setFoo('FOO'); 208 | $complex->setNested($nested); 209 | 210 | $encoded = $complex->toStream(); 211 | $expected = $this->executeProtoc($input, $class, $proto); 212 | $decoded = Complex::fromStream(Stream::wrap($expected)); 213 | 214 | $this->assertInstanceOf(Complex::CLASS, $decoded); 215 | $this->assertInstanceOf(Complex\Nested::CLASS, $complex->getNested()); 216 | $this->assertEquals(bin2hex($encoded), bin2hex($expected)); 217 | $this->assertEquals($complex->getNested()->getFoo(), 'FOO'); 218 | } 219 | 220 | protected function executeProtoc($input, $class, $proto) 221 | { 222 | $path = __DIR__ . '/Resources'; 223 | $command = "echo '$input' | protoc --encode=$class -I$path $path/$proto.proto"; 224 | $output = null; 225 | $exitCode = null; 226 | 227 | exec($command, $output, $exitCode); 228 | 229 | if ($exitCode !== 0) { 230 | $this->fail("Fail to run protoc : [$command]"); 231 | } 232 | 233 | return implode(PHP_EOL, $output); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/Stream.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Stream 14 | { 15 | /** 16 | * @var resource 17 | */ 18 | private $stream; 19 | 20 | /** 21 | * @var integer 22 | */ 23 | private $size; 24 | 25 | /** 26 | * @param resource $stream 27 | * @param integer $size 28 | * 29 | * @throws \InvalidArgumentException if the stream is not a stream resource 30 | */ 31 | public function __construct($stream, $size = null) 32 | { 33 | if ( ! is_resource($stream)) { 34 | throw new InvalidArgumentException('Stream must be a resource'); 35 | } 36 | 37 | $this->size = $size; 38 | $this->stream = $stream; 39 | } 40 | 41 | /** 42 | * Closes the stream when the destructed 43 | */ 44 | public function __destruct() 45 | { 46 | if (is_resource($this->stream)) { 47 | fclose($this->stream); 48 | } 49 | 50 | $this->stream = null; 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function __toString() 57 | { 58 | return $this->getContents(); 59 | } 60 | 61 | /** 62 | * Returns the remaining contents of the stream as a string. 63 | * 64 | * @return string 65 | */ 66 | public function getContents() 67 | { 68 | if ( ! $this->stream) { 69 | return ''; 70 | } 71 | 72 | $this->seek(0); 73 | 74 | return stream_get_contents($this->stream); 75 | } 76 | 77 | /** 78 | * Get the size of the stream 79 | * 80 | * @return int|null Returns the size in bytes if known 81 | * 82 | * @throws \InvalidArgumentException If cannot find out the stream size 83 | */ 84 | public function getSize() 85 | { 86 | if ($this->size !== null) { 87 | return $this->size; 88 | } 89 | 90 | if ( ! $this->stream) { 91 | return null; 92 | } 93 | 94 | $stats = fstat($this->stream); 95 | 96 | if (isset($stats['size'])) { 97 | return $this->size = $stats['size']; 98 | } 99 | 100 | throw new RuntimeException('Unknown stream size'); 101 | } 102 | 103 | /** 104 | * Returns true if the stream is at the end of the stream. 105 | * 106 | * @return bool 107 | */ 108 | public function eof() 109 | { 110 | return feof($this->stream); 111 | } 112 | 113 | /** 114 | * Returns the current position of the file read/write pointer 115 | * 116 | * @return int 117 | * 118 | * @throws \RuntimeException If cannot find out the stream position 119 | */ 120 | public function tell() 121 | { 122 | $position = ftell($this->stream); 123 | 124 | if ($position === false) { 125 | throw new RuntimeException('Unable to get stream position'); 126 | } 127 | 128 | return $position; 129 | } 130 | 131 | /** 132 | * Seek to a position in the stream 133 | * 134 | * @param int $offset 135 | * @param int $whence 136 | * 137 | * @throws \RuntimeException If cannot find out the stream position 138 | */ 139 | public function seek($offset, $whence = SEEK_SET) 140 | { 141 | if (fseek($this->stream, $offset, $whence) !== 0) { 142 | throw new RuntimeException('Unable to seek stream position to ' . $offset); 143 | } 144 | } 145 | 146 | /** 147 | * Read data from the stream 148 | * 149 | * @param int $length 150 | * 151 | * @return string 152 | */ 153 | public function read($length) 154 | { 155 | if ($length < 1) { 156 | return ''; 157 | } 158 | 159 | $buffer = fread($this->stream, $length); 160 | 161 | if ($buffer === false) { 162 | throw new RuntimeException('Failed to read ' . $length . ' bytes'); 163 | } 164 | 165 | return $buffer; 166 | } 167 | 168 | /** 169 | * Read stream 170 | * 171 | * @param int $length 172 | * 173 | * @return \Protobuf\Stream 174 | * 175 | * @throws \RuntimeException 176 | */ 177 | public function readStream($length) 178 | { 179 | $stream = self::fromString(); 180 | $target = $stream->stream; 181 | $source = $this->stream; 182 | 183 | if ($length < 1) { 184 | return $stream; 185 | } 186 | 187 | $written = stream_copy_to_stream($source, $target, $length); 188 | 189 | if ($written !== $length) { 190 | throw new RuntimeException('Failed to read stream with ' . $length . ' bytes'); 191 | } 192 | 193 | $stream->seek(0); 194 | 195 | return $stream; 196 | } 197 | 198 | /** 199 | * Write data to the stream 200 | * 201 | * @param string $bytes 202 | * @param int $length 203 | * 204 | * @return int 205 | * 206 | * @throws \RuntimeException 207 | */ 208 | public function write($bytes, $length) 209 | { 210 | $written = fwrite($this->stream, $bytes, $length); 211 | 212 | if ($written !== $length) { 213 | throw new RuntimeException('Failed to write '.$length.' bytes'); 214 | } 215 | 216 | $this->size = null; 217 | 218 | return $written; 219 | } 220 | 221 | /** 222 | * Write stream 223 | * 224 | * @param \Protobuf\Stream $stream 225 | * @param int $length 226 | * 227 | * @return int 228 | * 229 | * @throws \RuntimeException 230 | */ 231 | public function writeStream(Stream $stream, $length) 232 | { 233 | $target = $this->stream; 234 | $source = $stream->stream; 235 | $written = stream_copy_to_stream($source, $target); 236 | 237 | if ($written !== $length) { 238 | throw new RuntimeException('Failed to write stream with ' . $length . ' bytes'); 239 | } 240 | 241 | $this->size = null; 242 | 243 | return $written; 244 | } 245 | 246 | /** 247 | * Wrap the input resource in a stream object. 248 | * 249 | * @param \Protobuf\Stream|resource|string $resource 250 | * @param integer $size 251 | * 252 | * @return \Protobuf\Stream 253 | * 254 | * @throws \InvalidArgumentException if the $resource arg is not valid. 255 | */ 256 | public static function wrap($resource = '', $size = null) 257 | { 258 | if ($resource instanceof Stream) { 259 | return $resource; 260 | } 261 | 262 | $type = gettype($resource); 263 | 264 | if ($type == 'string') { 265 | return self::fromString($resource, $size); 266 | } 267 | 268 | if ($type == 'resource') { 269 | return new self($resource, $size); 270 | } 271 | 272 | throw new InvalidArgumentException('Invalid resource type: ' . $type); 273 | } 274 | 275 | /** 276 | * Create a new stream. 277 | * 278 | * @return \Protobuf\Stream 279 | */ 280 | public static function create() 281 | { 282 | return new self(fopen('php://temp', 'r+')); 283 | } 284 | 285 | /** 286 | * Create a new stream from a string. 287 | * 288 | * @param string $resource 289 | * @param integer $size 290 | * 291 | * @return \Protobuf\Stream 292 | */ 293 | public static function fromString($resource = '', $size = null) 294 | { 295 | $stream = fopen('php://temp', 'r+'); 296 | 297 | if ($resource !== '') { 298 | fwrite($stream, $resource); 299 | fseek($stream, 0); 300 | } 301 | 302 | return new self($stream, $size); 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /src/Binary/StreamWriter.php: -------------------------------------------------------------------------------- 1 | 14 | * @author Fabio B. Silva 15 | */ 16 | class StreamWriter 17 | { 18 | /** 19 | * @var \Protobuf\Configuration 20 | */ 21 | protected $config; 22 | 23 | /** 24 | * @var \Protobuf\Binary\Platform\NegativeEncoder 25 | */ 26 | protected $negativeEncoder; 27 | 28 | /** 29 | * @var bool 30 | */ 31 | protected $isBigEndian; 32 | 33 | /** 34 | * Constructor 35 | * 36 | * @param \Protobuf\Configuration $config 37 | */ 38 | public function __construct(Configuration $config) 39 | { 40 | $this->config = $config; 41 | $this->isBigEndian = BigEndian::isBigEndian(); 42 | $this->negativeEncoder = $config->getPlatformFactory() 43 | ->getNegativeEncoder(); 44 | } 45 | 46 | /** 47 | * Store the given bytes in the stream. 48 | * 49 | * @param \Protobuf\Stream $stream 50 | * @param string $bytes 51 | * @param int $length 52 | */ 53 | public function writeBytes(Stream $stream, $bytes, $length = null) 54 | { 55 | if ($length === null) { 56 | $length = mb_strlen($bytes, '8bit'); 57 | } 58 | 59 | $stream->write($bytes, $length); 60 | } 61 | 62 | /** 63 | * Store a single byte. 64 | * 65 | * @param \Protobuf\Stream $stream 66 | * @param integer $value 67 | */ 68 | public function writeByte(Stream $stream, $value) 69 | { 70 | $stream->write(chr($value), 1); 71 | } 72 | 73 | /** 74 | * Store an integer encoded as varint. 75 | * 76 | * @param \Protobuf\Stream $stream 77 | * @param integer $value 78 | */ 79 | public function writeVarint(Stream $stream, $value) 80 | { 81 | // Small values do not need to be encoded 82 | if ($value >= 0 && $value < 0x80) { 83 | $this->writeByte($stream, $value); 84 | 85 | return; 86 | } 87 | 88 | $values = null; 89 | 90 | // Build an array of bytes with the encoded values 91 | if ($value > 0) { 92 | $values = []; 93 | 94 | while ($value > 0) { 95 | $values[] = 0x80 | ($value & 0x7f); 96 | $value = $value >> 7; 97 | } 98 | } 99 | 100 | if ($values === null) { 101 | $values = $this->negativeEncoder->encodeVarint($value); 102 | } 103 | 104 | // Remove the MSB flag from the last byte 105 | $values[count($values) - 1] &= 0x7f; 106 | 107 | // Convert the byte sized ints to actual bytes in a string 108 | $values = array_merge(['C*'], $values); 109 | $bytes = call_user_func_array('pack', $values); 110 | 111 | $this->writeBytes($stream, $bytes); 112 | } 113 | 114 | /** 115 | * Encodes an integer with zigzag. 116 | * 117 | * @param \Protobuf\Stream $stream 118 | * @param integer $value 119 | * @param integer $base 120 | */ 121 | public function writeZigzag(Stream $stream, $value, $base = 32) 122 | { 123 | if ($base == 32) { 124 | $this->writeZigzag32($stream, $value); 125 | 126 | return; 127 | } 128 | 129 | $this->writeZigzag64($stream, $value); 130 | } 131 | 132 | /** 133 | * Encodes an integer with zigzag. 134 | * 135 | * @param \Protobuf\Stream $stream 136 | * @param integer $value 137 | */ 138 | public function writeZigzag32(Stream $stream, $value) 139 | { 140 | $this->writeVarint($stream, ($value << 1) ^ ($value >> 32 - 1)); 141 | } 142 | 143 | /** 144 | * Encodes an integer with zigzag. 145 | * 146 | * @param \Protobuf\Stream $stream 147 | * @param integer $value 148 | */ 149 | public function writeZigzag64(Stream $stream, $value) 150 | { 151 | $this->writeVarint($stream, ($value << 1) ^ ($value >> 64 - 1)); 152 | } 153 | 154 | /** 155 | * Encode an integer as a fixed of 32bits with sign. 156 | * 157 | * @param \Protobuf\Stream $stream 158 | * @param integer $value 159 | */ 160 | public function writeSFixed32(Stream $stream, $value) 161 | { 162 | $bytes = pack('l*', $value); 163 | 164 | if ($this->isBigEndian) { 165 | $bytes = strrev($bytes); 166 | } 167 | 168 | $this->writeBytes($stream, $bytes); 169 | } 170 | 171 | /** 172 | * Encode an integer as a fixed of 32bits without sign. 173 | * 174 | * @param \Protobuf\Stream $stream 175 | * @param integer $value 176 | */ 177 | public function writeFixed32(Stream $stream, $value) 178 | { 179 | $this->writeBytes($stream, pack('V*', $value), 4); 180 | } 181 | 182 | /** 183 | * Encode an integer as a fixed of 64bits with sign. 184 | * 185 | * @param \Protobuf\Stream $stream 186 | * @param integer $value 187 | */ 188 | public function writeSFixed64(Stream $stream, $value) 189 | { 190 | if ($value >= 0) { 191 | $this->writeFixed64($stream, $value); 192 | 193 | return; 194 | } 195 | 196 | $bytes = $this->negativeEncoder->encodeSFixed64($value); 197 | 198 | $this->writeBytes($stream, $bytes); 199 | } 200 | 201 | /** 202 | * Encode an integer as a fixed of 64bits without sign. 203 | * 204 | * @param \Protobuf\Stream $stream 205 | * @param integer $value 206 | */ 207 | public function writeFixed64(Stream $stream, $value) 208 | { 209 | $bytes = pack('V*', $value & 0xffffffff, $value / (0xffffffff + 1)); 210 | 211 | $this->writeBytes($stream, $bytes, 8); 212 | } 213 | 214 | /** 215 | * Encode a number as a 32bit float. 216 | * 217 | * @param \Protobuf\Stream $stream 218 | * @param float $value 219 | */ 220 | public function writeFloat(Stream $stream, $value) 221 | { 222 | $bytes = pack('f*', $value); 223 | 224 | if ($this->isBigEndian) { 225 | $bytes = strrev($bytes); 226 | } 227 | 228 | $this->writeBytes($stream, $bytes, 4); 229 | } 230 | 231 | /** 232 | * Encode a number as a 64bit double. 233 | * 234 | * @param \Protobuf\Stream $stream 235 | * @param float $value 236 | */ 237 | public function writeDouble(Stream $stream, $value) 238 | { 239 | $bytes = pack('d*', $value); 240 | 241 | if ($this->isBigEndian) { 242 | $bytes = strrev($bytes); 243 | } 244 | 245 | $this->writeBytes($stream, $bytes, 8); 246 | } 247 | 248 | /** 249 | * Encode a bool. 250 | * 251 | * @param \Protobuf\Stream $stream 252 | * @param bool $value 253 | */ 254 | public function writeBool(Stream $stream, $value) 255 | { 256 | $this->writeVarint($stream, $value ? 1 : 0); 257 | } 258 | 259 | /** 260 | * Encode a string. 261 | * 262 | * @param \Protobuf\Stream $stream 263 | * @param string $value 264 | */ 265 | public function writeString(Stream $stream, $value) 266 | { 267 | $this->writeVarint($stream, mb_strlen($value, '8bit')); 268 | $this->writeBytes($stream, $value); 269 | } 270 | 271 | /** 272 | * Encode a stream of bytes. 273 | * 274 | * @param \Protobuf\Stream $stream 275 | * @param \Protobuf\Stream $value 276 | */ 277 | public function writeByteStream(Stream $stream, Stream $value) 278 | { 279 | $length = $value->getSize(); 280 | 281 | $value->seek(0); 282 | $this->writeVarint($stream, $length); 283 | $stream->writeStream($value, $length); 284 | } 285 | 286 | /** 287 | * Write the given stream. 288 | * 289 | * @param \Protobuf\Stream $stream 290 | * @param \Protobuf\Stream $value 291 | * @param int $length 292 | */ 293 | public function writeStream(Stream $stream, Stream $value, $length = null) 294 | { 295 | if ($length === null) { 296 | $length = $value->getSize(); 297 | } 298 | 299 | $stream->writeStream($value, $length); 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/Binary/StreamReader.php: -------------------------------------------------------------------------------- 1 | 17 | * @author Fabio B. Silva 18 | */ 19 | class StreamReader 20 | { 21 | /** 22 | * @var \Protobuf\Configuration 23 | */ 24 | protected $config; 25 | 26 | /** 27 | * @var \Protobuf\Stream 28 | */ 29 | protected $stream; 30 | 31 | /** 32 | * @var bool 33 | */ 34 | protected $isBigEndian; 35 | 36 | /** 37 | * Constructor 38 | * 39 | * @param \Protobuf\Configuration $config 40 | */ 41 | public function __construct(Configuration $config) 42 | { 43 | $this->config = $config; 44 | $this->isBigEndian = BigEndian::isBigEndian(); 45 | } 46 | 47 | /** 48 | * Reads a byte. 49 | * 50 | * @param \Protobuf\Stream $stream 51 | * 52 | * @return integer 53 | */ 54 | public function readByte(Stream $stream) 55 | { 56 | $char = $stream->read(1); 57 | $byte = ord($char); 58 | 59 | return $byte; 60 | } 61 | 62 | /** 63 | * Decode a varint. 64 | * 65 | * @param \Protobuf\Stream $stream 66 | * 67 | * @return integer 68 | */ 69 | public function readVarint(Stream $stream) 70 | { 71 | // Optimize common case (single byte varints) 72 | $byte = $this->readByte($stream); 73 | 74 | if ($byte < 0x80) { 75 | return $byte; 76 | } 77 | 78 | $length = $stream->getSize(); 79 | $offset = $stream->tell(); 80 | $result = $byte & 0x7f; 81 | $shift = 7; 82 | 83 | // fastpath 32bit varints (5bytes) by unrolling the loop 84 | if ($length - $offset >= 4) { 85 | // 2 86 | $byte = $this->readByte($stream); 87 | $result |= ($byte & 0x7f) << 7; 88 | 89 | if ($byte < 0x80) { 90 | return $result; 91 | } 92 | 93 | // 3 94 | $byte = $this->readByte($stream); 95 | $result |= ($byte & 0x7f) << 14; 96 | 97 | if ($byte < 0x80) { 98 | return $result; 99 | } 100 | 101 | // 4 102 | $byte = $this->readByte($stream); 103 | $result |= ($byte & 0x7f) << 21; 104 | 105 | if ($byte < 0x80) { 106 | return $result; 107 | } 108 | 109 | // 5 110 | $byte = $this->readByte($stream); 111 | $result |= ($byte & 0x7f) << 28; 112 | 113 | if ($byte < 0x80) { 114 | return $result; 115 | } 116 | 117 | $shift = 35; 118 | } 119 | 120 | // If we're just at the end of the buffer or handling a 64bit varint 121 | do { 122 | $byte = $this->readByte($stream); 123 | $result |= ($byte & 0x7f) << $shift; 124 | $shift += 7; 125 | } while ($byte > 0x7f); 126 | 127 | return $result; 128 | } 129 | 130 | /** 131 | * Decodes a zigzag integer of the given bits. 132 | * 133 | * @param \Protobuf\Stream $stream 134 | * 135 | * @return integer 136 | */ 137 | public function readZigzag(Stream $stream) 138 | { 139 | $number = $this->readVarint($stream); 140 | $zigzag = ($number >> 1) ^ (-($number & 1)); 141 | 142 | return $zigzag; 143 | } 144 | 145 | /** 146 | * Decode a fixed 32bit integer with sign. 147 | * 148 | * @param \Protobuf\Stream $stream 149 | * 150 | * @return integer 151 | */ 152 | public function readSFixed32(Stream $stream) 153 | { 154 | $bytes = $stream->read(4); 155 | 156 | if ($this->isBigEndian) { 157 | $bytes = strrev($bytes); 158 | } 159 | 160 | list(, $result) = unpack('l', $bytes); 161 | 162 | return $result; 163 | } 164 | 165 | /** 166 | * Decode a fixed 32bit integer without sign. 167 | * 168 | * @param \Protobuf\Stream $stream 169 | * 170 | * @return integer 171 | */ 172 | public function readFixed32(Stream $stream) 173 | { 174 | $bytes = $stream->read(4); 175 | 176 | if (PHP_INT_SIZE < 8) { 177 | list(, $lo, $hi) = unpack('v*', $bytes); 178 | 179 | return $hi << 16 | $lo; 180 | } 181 | 182 | list(, $result) = unpack('V*', $bytes); 183 | 184 | return $result; 185 | } 186 | 187 | /** 188 | * Decode a fixed 64bit integer with sign. 189 | * 190 | * @param \Protobuf\Stream $stream 191 | * 192 | * @return integer 193 | */ 194 | public function readSFixed64(Stream $stream) 195 | { 196 | $bytes = $stream->read(8); 197 | 198 | list(, $lo0, $lo1, $hi0, $hi1) = unpack('v*', $bytes); 199 | 200 | return ($hi1 << 16 | $hi0) << 32 | ($lo1 << 16 | $lo0); 201 | } 202 | 203 | /** 204 | * Decode a fixed 64bit integer without sign. 205 | * 206 | * @param \Protobuf\Stream $stream 207 | * 208 | * @return integer 209 | */ 210 | public function readFixed64(Stream $stream) 211 | { 212 | return $this->readSFixed64($stream); 213 | } 214 | 215 | /** 216 | * Decode a 32bit float. 217 | * 218 | * @param \Protobuf\Stream $stream 219 | * 220 | * @return float 221 | */ 222 | public function readFloat(Stream $stream) 223 | { 224 | $bytes = $stream->read(4); 225 | 226 | if ($this->isBigEndian) { 227 | $bytes = strrev($bytes); 228 | } 229 | 230 | list(, $result) = unpack('f', $bytes); 231 | 232 | return $result; 233 | } 234 | 235 | /** 236 | * Decode a 64bit double. 237 | * 238 | * @param \Protobuf\Stream $stream 239 | * 240 | * @return float 241 | */ 242 | public function readDouble(Stream $stream) 243 | { 244 | $bytes = $stream->read(8); 245 | 246 | if ($this->isBigEndian) { 247 | $bytes = strrev($bytes); 248 | } 249 | 250 | list(, $result) = unpack('d', $bytes); 251 | 252 | return $result; 253 | } 254 | 255 | /** 256 | * Decode a bool. 257 | * 258 | * @param \Protobuf\Stream $stream 259 | * 260 | * @return bool 261 | */ 262 | public function readBool(Stream $stream) 263 | { 264 | return (bool) $this->readVarint($stream); 265 | } 266 | 267 | /** 268 | * Decode a string. 269 | * 270 | * @param \Protobuf\Stream $stream 271 | * 272 | * @return string 273 | */ 274 | public function readString(Stream $stream) 275 | { 276 | $length = $this->readVarint($stream); 277 | $string = $stream->read($length); 278 | 279 | return $string; 280 | } 281 | 282 | /** 283 | * Decode a stream of bytes. 284 | * 285 | * @param \Protobuf\Stream $stream 286 | * 287 | * @return \Protobuf\Stream 288 | */ 289 | public function readByteStream(Stream $stream) 290 | { 291 | $length = $this->readVarint($stream); 292 | $value = $stream->readStream($length); 293 | 294 | return $value; 295 | } 296 | 297 | /** 298 | * Read unknown scalar value. 299 | * 300 | * @param \Protobuf\Stream $stream 301 | * @param integer $wire 302 | * 303 | * @return scalar 304 | */ 305 | public function readUnknown(Stream $stream, $wire) 306 | { 307 | if ($wire === WireFormat::WIRE_VARINT) { 308 | return $this->readVarint($stream); 309 | } 310 | 311 | if ($wire === WireFormat::WIRE_LENGTH) { 312 | return $this->readString($stream); 313 | } 314 | 315 | if ($wire === WireFormat::WIRE_FIXED32) { 316 | return $this->readFixed32($stream); 317 | } 318 | 319 | if ($wire === WireFormat::WIRE_FIXED64) { 320 | return $this->readFixed64($stream); 321 | } 322 | 323 | if ($wire === WireFormat::WIRE_GROUP_START || $wire === WireFormat::WIRE_GROUP_END) { 324 | throw new RuntimeException('Groups are deprecated in Protocol Buffers and unsupported.'); 325 | } 326 | 327 | throw new RuntimeException("Unsupported wire type '$wire' while reading unknown field."); 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /tests/Binary/SizeCalculatorTest.php: -------------------------------------------------------------------------------- 1 | writer = new StreamWriter($this->config); 27 | $this->calculator = new SizeCalculator($this->config); 28 | } 29 | 30 | public function varintProvider() 31 | { 32 | return [ 33 | [1], 34 | [-1], 35 | [123456789], 36 | [-123456789], 37 | [123456789123456789], 38 | [-123456789123456789] 39 | ]; 40 | } 41 | 42 | /** 43 | * @dataProvider varintProvider 44 | */ 45 | public function testComputeVarintSize($value) 46 | { 47 | $stream = Stream::create(); 48 | 49 | $this->writer->writeVarint($stream, $value); 50 | 51 | $streamSize = $stream->getSize(); 52 | $actualSize = $this->calculator->computeVarintSize($value); 53 | 54 | $this->assertEquals($streamSize, $actualSize); 55 | } 56 | 57 | public function providerZigZag32() 58 | { 59 | return [ 60 | [1], 61 | [-1], 62 | [123456789], 63 | [-123456789] 64 | ]; 65 | } 66 | 67 | /** 68 | * @dataProvider providerZigZag32 69 | */ 70 | public function testComputeZigZag32Size($value) 71 | { 72 | $stream = Stream::create(); 73 | 74 | $this->writer->writeZigZag32($stream, $value); 75 | 76 | $streamSize = $stream->getSize(); 77 | $actualSize = $this->calculator->computeZigzag32Size($value); 78 | 79 | $this->assertEquals($streamSize, $actualSize); 80 | } 81 | 82 | public function providerZigZag64() 83 | { 84 | return [ 85 | [1], 86 | [-1], 87 | [123456789], 88 | [-123456789] 89 | ]; 90 | } 91 | 92 | /** 93 | * @dataProvider providerZigZag64 94 | */ 95 | public function testComputeZigZag64Size($value) 96 | { 97 | $stream = Stream::create(); 98 | 99 | $this->writer->writeZigZag64($stream, $value); 100 | 101 | $streamSize = $stream->getSize(); 102 | $actualSize = $this->calculator->computeZigzag64Size($value); 103 | 104 | $this->assertEquals($streamSize, $actualSize); 105 | } 106 | 107 | public function providerSFixed32() 108 | { 109 | return [ 110 | [1], 111 | [-1], 112 | [123456789], 113 | [-123456789] 114 | ]; 115 | } 116 | 117 | /** 118 | * @dataProvider providerSFixed32 119 | */ 120 | public function testComputeSFixed32Size($value) 121 | { 122 | $stream = Stream::create(); 123 | 124 | $this->writer->writeSFixed32($stream, $value); 125 | 126 | $streamSize = $stream->getSize(); 127 | $actualSize = $this->calculator->computeSFixed32Size($value); 128 | 129 | $this->assertEquals($streamSize, $actualSize); 130 | } 131 | 132 | public function providerFixed32() 133 | { 134 | return [ 135 | [1], 136 | [1000], 137 | [123456789] 138 | ]; 139 | } 140 | 141 | /** 142 | * @dataProvider providerFixed32 143 | */ 144 | public function testComputeFixed32Size($value) 145 | { 146 | $stream = Stream::create(); 147 | 148 | $this->writer->writeFixed32($stream, $value); 149 | 150 | $streamSize = $stream->getSize(); 151 | $actualSize = $this->calculator->computeFixed32Size($value); 152 | 153 | $this->assertEquals($streamSize, $actualSize); 154 | } 155 | 156 | public function providerSFixed64() 157 | { 158 | return [ 159 | [1], 160 | [-1], 161 | [123456789], 162 | [-123456789], 163 | [123456789123456789], 164 | [-123456789123456789] 165 | ]; 166 | } 167 | 168 | /** 169 | * @dataProvider providerSFixed64 170 | */ 171 | public function testComputeSFixed64Size($value) 172 | { 173 | $stream = Stream::create(); 174 | 175 | $this->writer->writeSFixed64($stream, $value); 176 | 177 | $streamSize = $stream->getSize(); 178 | $actualSize = $this->calculator->computeSFixed64Size($value); 179 | 180 | $this->assertEquals($streamSize, $actualSize); 181 | } 182 | 183 | public function providerFixed64() 184 | { 185 | return [ 186 | [1], 187 | [123456789], 188 | [123456789123456789] 189 | ]; 190 | } 191 | 192 | /** 193 | * @dataProvider providerFixed64 194 | */ 195 | public function testComputeFixed64Size($value) 196 | { 197 | $stream = Stream::create(); 198 | 199 | $this->writer->writeFixed64($stream, $value); 200 | 201 | $streamSize = $stream->getSize(); 202 | $actualSize = $this->calculator->computeFixed64Size($value); 203 | 204 | $this->assertEquals($streamSize, $actualSize); 205 | } 206 | 207 | public function providerFloat() 208 | { 209 | return [ 210 | [1.1], 211 | [-1.1], 212 | [123456789.2], 213 | [-123456789.2], 214 | [12345.123046875], 215 | [-12345.123046875] 216 | ]; 217 | } 218 | 219 | /** 220 | * @dataProvider providerFloat 221 | */ 222 | public function testComputeFloatSize($value) 223 | { 224 | $stream = Stream::create(); 225 | 226 | $this->writer->writeFloat($stream, $value); 227 | 228 | $streamSize = $stream->getSize(); 229 | $actualSize = $this->calculator->computeFloatSize($value); 230 | 231 | $this->assertEquals($streamSize, $actualSize); 232 | } 233 | 234 | public function providerDouble() 235 | { 236 | return [ 237 | [1.1], 238 | [-1.1], 239 | [12345.12345], 240 | [-12345.12345], 241 | [123456789.12345], 242 | [-123456789.12345] 243 | ]; 244 | } 245 | 246 | /** 247 | * @dataProvider providerDouble 248 | */ 249 | public function testComputeDoubleSize($value) 250 | { 251 | $stream = Stream::create(); 252 | 253 | $this->writer->writeDouble($stream, $value); 254 | 255 | $streamSize = $stream->getSize(); 256 | $actualSize = $this->calculator->computeDoubleSize($value); 257 | 258 | $this->assertEquals($streamSize, $actualSize); 259 | } 260 | 261 | public function providerBool() 262 | { 263 | return [ 264 | [1], 265 | [0], 266 | [true], 267 | [false] 268 | ]; 269 | } 270 | 271 | /** 272 | * @dataProvider providerBool 273 | */ 274 | public function testComputeBoolSize($value) 275 | { 276 | $stream = Stream::create(); 277 | 278 | $this->writer->writeBool($stream, $value); 279 | 280 | $streamSize = $stream->getSize(); 281 | $actualSize = $this->calculator->computeBoolSize($value); 282 | 283 | $this->assertEquals($streamSize, $actualSize); 284 | } 285 | 286 | public function providerString() 287 | { 288 | return [ 289 | ['foo'], 290 | ['http://www.lipsum.com/'], 291 | ['Neque porro quisquam est qui dolorem ipsum quia dolor sit amet'] 292 | ]; 293 | } 294 | 295 | /** 296 | * @dataProvider providerString 297 | */ 298 | public function testComputeStringSize($value) 299 | { 300 | $stream = Stream::create(); 301 | 302 | $this->writer->writeString($stream, $value); 303 | 304 | $streamSize = $stream->getSize(); 305 | $actualSize = $this->calculator->computeStringSize($value); 306 | 307 | $this->assertEquals($streamSize, $actualSize); 308 | } 309 | 310 | public function providerByteStream() 311 | { 312 | return [ 313 | [Stream::create('foo')], 314 | [Stream::create('http://www.lipsum.com/')], 315 | [Stream::create('Neque porro quisquam est qui dolorem ipsum quia dolor sit amet')] 316 | ]; 317 | } 318 | 319 | /** 320 | * @dataProvider providerByteStream 321 | */ 322 | public function testComputeByteStreamSize($value) 323 | { 324 | $stream = Stream::create(); 325 | 326 | $this->writer->writeByteStream($stream, $value); 327 | 328 | $streamSize = $stream->getSize(); 329 | $actualSize = $this->calculator->computeByteStreamSize($value); 330 | 331 | $this->assertEquals($streamSize, $actualSize); 332 | } 333 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Protobuf for PHP 2 | ================ 3 | 4 | [![Build Status](https://travis-ci.org/protobuf-php/protobuf.svg?branch=master)](https://travis-ci.org/protobuf-php/protobuf) 5 | [![Coverage Status](https://coveralls.io/repos/protobuf-php/protobuf/badge.svg?branch=master&service=github)](https://coveralls.io/github/protobuf-php/protobuf?branch=master) 6 | [![Total Downloads](https://poser.pugx.org/protobuf-php/protobuf/downloads)](https://packagist.org/packages/protobuf-php/protobuf) 7 | [![License](https://poser.pugx.org/protobuf-php/protobuf/license)](https://packagist.org/packages/protobuf-php/protobuf) 8 | 9 | Protobuf for PHP is an implementation of Google's Protocol Buffers for the PHP 10 | language, supporting its binary data serialization and including a `protoc` 11 | plugin to generate PHP classes from .proto files. 12 | 13 | 14 | ## Installation 15 | 16 | Run the following `composer` commands: 17 | 18 | ```console 19 | $ composer require "protobuf-php/protobuf" 20 | ``` 21 | 22 | 23 | ## Overview 24 | 25 | This tutorial provides a basic introduction to working with protocol buffers. 26 | By walking through creating a simple example application, it shows you how to 27 | 28 | * Define message formats in a ```.proto``` file. 29 | * Use the protocol buffer compiler. 30 | * Use the PHP protocol buffer API to write and read messages. 31 | 32 | 33 | #### Why Use Protocol Buffers? 34 | 35 | The example we're going to use is a very simple "address book" application that can read and write people's contact details to and from a file. Each person in the address book has a name, an ID, an email address, and a contact phone number. 36 | 37 | How do you serialize and retrieve structured data like this? 38 | There are a few ways to solve this problem: 39 | 40 | * Use PHP Serialization. This is the default approach since it's built into the language, but it is not very space efficient, and also doesn't work very well if you need to share data with applications written in other languages (Nodejs,Java,Python, etc..). 41 | 42 | * You can invent an ad-hoc way to encode the data items into a single string – such as encoding 4 ints as "12:3:-23:67". This is a simple and flexible approach, although it does require writing one-off encoding and parsing code, and the parsing imposes a small run-time cost. This works best for encoding very simple data. 43 | 44 | * Serialize the data to XML. This approach can be very attractive since XML is (sort of) human readable and there are binding libraries for lots of languages. This can be a good choice if you want to share data with other applications/projects. However, XML is notoriously space intensive, and encoding/decoding it can impose a huge performance penalty on applications. Also, navigating an XML DOM tree is considerably more complicated than navigating simple fields in a class normally would be. 45 | 46 | 47 | Protocol buffers are the flexible, efficient, automated solution to solve exactly this problem. With protocol buffers, you write a ```.proto``` description of the data structure you wish to store. From that, the protocol buffer compiler creates a class that implements automatic encoding and parsing of the protocol buffer data with an efficient binary format. The generated class provides getters and setters for the fields that make up a protocol buffer and takes care of the details of reading and writing the protocol buffer as a unit. Importantly, the protocol buffer format supports the idea of extending the format over time in such a way that the code can still read data encoded with the old format. 48 | 49 | 50 | #### Defining Your Protocol Format 51 | 52 | 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```. 53 | 54 | ```proto 55 | package tutorial; 56 | import "php.proto"; 57 | option (php.package) = "Tutorial.AddressBookProtos"; 58 | 59 | message Person { 60 | required string name = 1; 61 | required int32 id = 2; 62 | optional string email = 3; 63 | 64 | enum PhoneType { 65 | MOBILE = 0; 66 | HOME = 1; 67 | WORK = 2; 68 | } 69 | 70 | message PhoneNumber { 71 | required string number = 1; 72 | optional PhoneType type = 2 [default = HOME]; 73 | } 74 | 75 | repeated PhoneNumber phone = 4; 76 | } 77 | 78 | message AddressBook { 79 | repeated Person person = 1; 80 | } 81 | ``` 82 | 83 | 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. 84 | The ```.proto``` file starts with a package declaration, which helps to prevent naming conflicts between different projects. 85 | In PHP, the package name is used as the PHP namespace unless you have explicitly specified a ```(php.package)```, as we have here. 86 | 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. 87 | 88 | 89 | After the package declaration, you can see two options that are PHP-specific: ```import "php.proto";``` and ```(php.package)```. 90 | * ```import "php.proto"``` add supports a few PHP specific options for proto files. 91 | * ```(php.package)``` specifies in what php namespace name your generated classes should live. 92 | If you don't specify this explicitly, it simply matches the package name given by the package declaration, but these names usually aren't appropriate PHP namespace names. 93 | 94 | 95 | Next, you have your message definitions. A message is just an aggregate containing a set of typed fields. Many standard simple data types are available as field types, including ```bool```, ```int32```, ```float```, ```double```, and ```string```. You can also add further structure to your messages by using other message types as field types – in the above example the ```Person``` message contains ```PhoneNumber``` messages, while the ```AddressBook``` message contains ```Person``` messages. You can even define message types nested inside other messages – as you can see, the ```PhoneNumber``` type is defined inside ```Person```. You can also define ```enum``` types if you want one of your fields to have one of a predefined list of values – here you want to specify that a phone number can be one of ```MOBILE```, ```HOME```, or ```WORK```. 96 | 97 | 98 | The ```" = 1"```, ```" = 2"``` markers on each element identify the unique ```tag``` that field uses in the binary encoding. Tag numbers 1-15 require one less byte to encode than higher numbers, so as an optimization you can decide to use those tags for the commonly used or repeated elements, leaving tags 16 and higher for less-commonly used optional elements. Each element in a repeated field requires re-encoding the tag number, so repeated fields are particularly good candidates for this optimization. 99 | 100 | 101 | Each field must be annotated with one of the following modifiers: 102 | 103 | * **required**: a value for the field must be provided, otherwise the message will be considered "uninitialized". Trying to build an uninitialized message will throw a RuntimeException. Parsing an uninitialized message will throw an IOException. Other than this, a required field behaves exactly like an optional field. 104 | * **optional**: the field may or may not be set. If an optional field value isn't set, a default value is used. For simple types, you can specify your own default value, as we've done for the phone number type in the example. Otherwise, a system default is used: zero for numeric types, the empty string for strings, false for bools. For embedded messages, the default value is always the "default instance" or "prototype" of the message, which has none of its fields set. Calling the accessor to get the value of an optional (or required) field which has not been explicitly set always returns that field's default value. 105 | * **repeated**: the field may be repeated any number of times (including zero). The order of the repeated values will be preserved in the protocol buffer. Think of repeated fields as dynamically sized arrays. 106 | 107 | 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. 108 | 109 | 110 | #### Compiling Your Protocol Buffers 111 | 112 | 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: 113 | 114 | If you haven't installed the compiler (```protoc```) or you dont have the php plugin, see https://github.com/protobuf-php/protobuf-plugin. 115 | 116 | Now run the compiler plugin, specifying the proto files source directory (the file directory is used if you don't provide a value), the destination directory (where you want the generated code to go), and the path to your ```.proto``` In this case: 117 | 118 | ```console 119 | php ./vendor/bin/protobuf --include-descriptors -i . -o ./src/ ./addressbook.proto 120 | ``` 121 | 122 | This generates the following PHP classes in your specified destination directory 123 | 124 | ```console 125 | src/ 126 | └── Tutorial 127 | └── AddressBookProtos 128 | ├── AddressBook.php 129 | ├── Person 130 | │   ├── PhoneNumber.php 131 | │   └── PhoneType.php 132 | └── Person.php 133 | ``` 134 | 135 | #### The Protocol Buffer API 136 | 137 | Let's look at some of the generated code and see what classes and methods the compiler has created for you. If you look in ```src/Tutorial/AddressBookProtos/Person.php``` you can see that it defines a class called ```Person```. 138 | 139 | Messages have auto-generated accessor methods for each field of the message. 140 | Here are some of the accessors for the Person class (implementations omitted for brevity): 141 | 142 | ```php 143 | */ 178 | public function getPhoneList(); 179 | /** @param \Protobuf\Collection<\ProtobufTest\Protos\Person\PhoneNumber> $value */ 180 | public function setPhoneList(\Protobuf\Collection $value); 181 | #################################################################################### 182 | ?> 183 | ``` 184 | 185 | As you can see, there are simple getters and setters for each field. 186 | There are also has getters for each singular field which return true if that field has been set. 187 | Repeated fields have a extra method, an add method which appends a new element to the list. 188 | 189 | Notice how these accessor methods use camel-case naming, even though the ```.proto``` file uses lowercase-with-underscores. 190 | This transformation is done automatically by the protocol buffer compiler so that the generated classes match standard PHP style conventions. 191 | You should always use lowercase-with-underscores for field names in your ```.proto``` files; this ensures good naming practice in all the generated languages. See the style guide for more on good ```.proto``` style. 192 | 193 | 194 | Protocol Buffers types map to the following PHP types: 195 | 196 | | Protocol Buffers | PHP | 197 | | ---------------- | ------------------ | 198 | | double | float | 199 | | float | float | 200 | | int32 | int | 201 | | int64 | int | 202 | | uint32 | int | 203 | | uint64 | int | 204 | | sint32 | int | 205 | | sint64 | int | 206 | | fixed32 | int | 207 | | fixed64 | int | 208 | | sfixed32 | int | 209 | | sfixed64 | int | 210 | | bool | bool | 211 | | string | string | 212 | | bytes | \\Protobuf\\Stream | 213 | 214 | 215 | #### Enums and Nested Classes 216 | 217 | The generated code includes a ```PhoneType``` [enum](https://github.com/protobuf-php/protobuf/blob/master/src/Enum.php): 218 | 219 | ```php 220 | 240 | ``` 241 | 242 | All nested types are generated using the parent class ```Person``` as part of its namespace. 243 | 244 | ```php 245 | setId(1); 253 | $person->setName('Fabio B. Silva'); 254 | $person->setEmail('fabio.bat.silva@gmail.com'); 255 | 256 | $phone->setType($type); 257 | $phone->setNumber('1231231212'); 258 | ?> 259 | ``` 260 | 261 | #### Known issues 262 | 263 | - Protobuf stores floating point values using the [IEEE 754](http://en.wikipedia.org/wiki/IEEE_754) standard 264 | with 64bit words for the `double` and 32bit for the `float` types. PHP supports IEEE 754 natively although 265 | the precission is platform dependant, however it typically supports 64bit doubles. It means that 266 | if your PHP was compiled with 64bit sized doubles (or greater) you shouldn't have any problem encoding 267 | and decoded float and double typed values. 268 | 269 | - Integer values are also [platform dependant in PHP](http://www.php.net/manual/en/language.types.integer.php). 270 | The library has been developed and tested against PHP binaries compiled with 64bit integers. The encoding and 271 | decoding algorithm should in theory work no matter if PHP uses 32bit or 64bit integers internally, just take 272 | into account that with 32bit integers the numbers cannot exceed in any case the `PHP_INT_MAX` value (2147483647). 273 | 274 | While Protobuf supports unsigned integers PHP does not. In fact, numbers above the compiled PHP maximum 275 | integer (`PHP_INT_MAX`, 0x7FFFFFFFFFFFFFFF for 64bits) will be automatically casted to doubles, which 276 | typically will offer 53bits of decimal precission, allowing to safely work with numbers upto 277 | 0x20000000000000 (2^53), even if they are represented in PHP as floats instead of integers. Higher numbers 278 | will loose precission or might even return an _infinity_ value, note that the library does not include 279 | any checking for these numbers and using them might provoke unexpected behaviour. 280 | 281 | Negative values when encoded as `int32`, `int64` or `fixed64` types require the big integer extensions 282 | [GMP](http://www.php.net/gmp) or [BC Math](http://www.php.net/bc) to be available in your PHP environment. 283 | The reason is that when encoding these negative numbers without using _zigzag_ the binary representation uses the most significant bit for the sign, thus the numbers become 284 | above the maximum supported values in PHP. The library will check for these conditions and will automatically 285 | try to use GMP or BC to process the value. 286 | 287 | 288 | #### Parsing and Serialization 289 | 290 | Each protocol buffer class has methods for writing and reading messages of your chosen type using the protocol buffer binary format. These include : 291 | 292 | 293 | ```php 294 | 331 | ``` 332 | 333 | 334 | #### Writing A Message 335 | 336 | Now let's try using your protocol buffer classes. The first thing you want your address book application to be able to do is write personal details to your address book file. To do this, you need to create and populate instances of your protocol buffer classes and then write them to an output stream. 337 | 338 | Here is a program which reads an ```AddressBook``` from a file, adds one new ```Person``` to it based on user input, and writes the new ```AddressBook``` back out to the file again. The parts which directly call or reference code generated by the protocol compiler are highlighted. 339 | 340 | 341 | ```php 342 | #!/usr/bin/env php 343 | setId($id); 359 | $person->setName($name); 360 | 361 | if ( ! empty($email)) { 362 | $person->setEmail($email); 363 | } 364 | 365 | while (true) { 366 | $number = trim(readline("Enter a phone number (or leave blank to finish):")); 367 | 368 | if (empty($number)) { 369 | break; 370 | } 371 | 372 | $phone = new Person\PhoneNumber(); 373 | $type = trim(readline("Is this a mobile, home, or work phone? ")); 374 | 375 | switch (strtolower($type)) { 376 | case 'mobile': 377 | $phone->setType(Person\PhoneType::MOBILE()); 378 | break; 379 | case 'work': 380 | $phone->setType(Person\PhoneType::WORK()); 381 | break; 382 | case 'home': 383 | $phone->setType(Person\PhoneType::HOME()); 384 | break; 385 | default: 386 | echo "Unknown phone type. Using default." . PHP_EOL; 387 | } 388 | 389 | $phone->setNumber($number); 390 | $person->addPhone($phone); 391 | } 392 | 393 | // Add a person. 394 | $addressBook->addPerson($person); 395 | 396 | // Print current address book 397 | echo $addressBook; 398 | 399 | // Write the new address book back to disk. 400 | file_put_contents($argv[1], $addressBook->toStream()); 401 | ?> 402 | ``` 403 | 404 | This tutorial documentation its based on the [Protocol Buffer Basics Tutorial](https://developers.google.com/protocol-buffers/docs/javatutorial). 405 | -------------------------------------------------------------------------------- /tests/SerializeMessageTest.php: -------------------------------------------------------------------------------- 1 | config->createComputeSizeContext(); 26 | $expectedSize = mb_strlen($expectedContent, '8bit'); 27 | $actualSize = $message->serializedSize($context); 28 | 29 | $this->assertEquals($expectedSize, $actualSize); 30 | } 31 | 32 | public function testWriteSimpleMessage() 33 | { 34 | $simple = new Simple(); 35 | 36 | $simple->setBool(true); 37 | $simple->setBytes("bar"); 38 | $simple->setString("foo"); 39 | $simple->setFloat(12345.123); 40 | $simple->setUint32(123456789); 41 | $simple->setInt32(-123456789); 42 | $simple->setFixed32(123456789); 43 | $simple->setSint32(-123456789); 44 | $simple->setSfixed32(-123456789); 45 | $simple->setDouble(123456789.12345); 46 | $simple->setInt64(-123456789123456789); 47 | $simple->setUint64(123456789123456789); 48 | $simple->setFixed64(123456789123456789); 49 | $simple->setSint64(-123456789123456789); 50 | $simple->setSfixed64(-123456789123456789); 51 | 52 | $expected = $this->getProtoContent('simple.bin'); 53 | $actual = $simple->toStream(); 54 | 55 | $this->assertEquals($expected, (string) $actual); 56 | $this->assertSerializedMessageSize($expected, $simple); 57 | } 58 | 59 | public function testWriteSimpleMessageTwice() 60 | { 61 | $simple = new Simple(); 62 | 63 | $simple->setBool(true); 64 | $simple->setBytes("bar"); 65 | $simple->setString("foo"); 66 | $simple->setFloat(12345.123); 67 | $simple->setUint32(123456789); 68 | $simple->setInt32(-123456789); 69 | $simple->setFixed32(123456789); 70 | $simple->setSint32(-123456789); 71 | $simple->setSfixed32(-123456789); 72 | $simple->setDouble(123456789.12345); 73 | $simple->setInt64(-123456789123456789); 74 | $simple->setUint64(123456789123456789); 75 | $simple->setFixed64(123456789123456789); 76 | $simple->setSint64(-123456789123456789); 77 | $simple->setSfixed64(-123456789123456789); 78 | 79 | $expected = $this->getProtoContent('simple.bin'); 80 | $actual1 = $simple->toStream(); 81 | $actual2 = $simple->toStream(); 82 | 83 | $this->assertEquals($expected, (string) $actual1); 84 | $this->assertEquals($expected, (string) $actual2); 85 | } 86 | 87 | public function testWriteRepeatedString() 88 | { 89 | $repeated = new Repeated(); 90 | 91 | $repeated->addString('one'); 92 | $repeated->addString('two'); 93 | $repeated->addString('three'); 94 | 95 | $expected = $this->getProtoContent('repeated-string.bin'); 96 | $actual = $repeated->toStream(); 97 | 98 | $this->assertEquals($expected, (string) $actual); 99 | $this->assertSerializedMessageSize($expected, $repeated); 100 | } 101 | 102 | public function testWriteRepeatedInt32() 103 | { 104 | $repeated = new Repeated(); 105 | 106 | $repeated->addInt(1); 107 | $repeated->addInt(2); 108 | $repeated->addInt(3); 109 | 110 | $expected = $this->getProtoContent('repeated-int32.bin'); 111 | $actual = $repeated->toStream(); 112 | 113 | $this->assertEquals($expected, (string) $actual); 114 | $this->assertSerializedMessageSize($expected, $repeated); 115 | } 116 | 117 | public function testWriteRepeatedNested() 118 | { 119 | $repeated = new Repeated(); 120 | $nested1 = new Repeated\Nested(); 121 | $nested2 = new Repeated\Nested(); 122 | $nested3 = new Repeated\Nested(); 123 | 124 | $nested1->setId(1); 125 | $nested2->setId(2); 126 | $nested3->setId(3); 127 | 128 | $repeated->addNested($nested1); 129 | $repeated->addNested($nested2); 130 | $repeated->addNested($nested3); 131 | 132 | $expected = $this->getProtoContent('repeated-nested.bin'); 133 | $actual = $repeated->toStream(); 134 | 135 | $this->assertEquals($expected, (string) $actual); 136 | $this->assertSerializedMessageSize($expected, $repeated); 137 | } 138 | 139 | public function testWriteRepeatedPacked() 140 | { 141 | $repeated = new Repeated(); 142 | 143 | $repeated->addPacked(1); 144 | $repeated->addPacked(2); 145 | $repeated->addPacked(3); 146 | 147 | $expected = $this->getProtoContent('repeated-packed.bin'); 148 | $actual = $repeated->toStream(); 149 | 150 | $this->assertEquals($expected, (string) $actual); 151 | $this->assertSerializedMessageSize($expected, $repeated); 152 | } 153 | 154 | public function testWriteRepeatedBytes() 155 | { 156 | $repeated = new Repeated(); 157 | 158 | $repeated->addBytes('bin1'); 159 | $repeated->addBytes('bin2'); 160 | $repeated->addBytes('bin3'); 161 | 162 | $expected = $this->getProtoContent('repeated-bytes.bin'); 163 | $actual = $repeated->toStream(); 164 | 165 | $this->assertEquals($expected, (string) $actual); 166 | $this->assertSerializedMessageSize($expected, $repeated); 167 | } 168 | 169 | public function testWriteRepeatedEnum() 170 | { 171 | $repeated = new Repeated(); 172 | 173 | $repeated->addEnum(Repeated\Enum::FOO()); 174 | $repeated->addEnum(Repeated\Enum::BAR()); 175 | 176 | $expected = $this->getProtoContent('repeated-enum.bin'); 177 | $actual = $repeated->toStream(); 178 | 179 | $this->assertEquals($expected, (string) $actual); 180 | $this->assertSerializedMessageSize($expected, $repeated); 181 | } 182 | 183 | public function testWriteRepeatedPackedEnum() 184 | { 185 | $repeated = new Repeated(); 186 | 187 | $repeated->addPackedEnum(Repeated\Enum::FOO()); 188 | $repeated->addPackedEnum(Repeated\Enum::BAR()); 189 | 190 | $expected = $this->getProtoContent('repeated-packed-enum.bin'); 191 | $actual = $repeated->toStream(); 192 | 193 | $this->assertEquals($expected, (string) $actual); 194 | $this->assertSerializedMessageSize($expected, $repeated); 195 | } 196 | 197 | public function testWriteComplexMessage() 198 | { 199 | $phone1 = new PhoneNumber(); 200 | $phone2 = new PhoneNumber(); 201 | $phone3 = new PhoneNumber(); 202 | $book = new AddressBook(); 203 | $person1 = new Person(); 204 | $person2 = new Person(); 205 | 206 | $person1->setId(2051); 207 | $person1->setName('John Doe'); 208 | $person1->setEmail('john.doe@gmail.com'); 209 | 210 | $person2->setId(23); 211 | $person2->setName('Iván Montes'); 212 | $person2->setEmail('drslump@pollinimini.net'); 213 | 214 | $book->addPerson($person1); 215 | $book->addPerson($person2); 216 | 217 | $person1->addPhone($phone1); 218 | $person1->addPhone($phone2); 219 | 220 | $phone1->setNumber('1231231212'); 221 | $phone1->setType(PhoneType::HOME()); 222 | 223 | $phone2->setNumber('55512321312'); 224 | $phone2->setType(PhoneType::MOBILE()); 225 | 226 | $phone3->setNumber('3493123123'); 227 | $phone3->setType(PhoneType::WORK()); 228 | 229 | $person2->addPhone($phone3); 230 | 231 | $expected = $this->getProtoContent('addressbook.bin'); 232 | $actual = $book->toStream(); 233 | 234 | $this->assertEquals($expected, (string) $actual); 235 | $this->assertSerializedMessageSize($expected, $book); 236 | } 237 | 238 | public function testWritePhpOptionsMessage() 239 | { 240 | $parentMessage = new ParentMessage(); 241 | $innerMessage1 = new ParentMessage\InnerMessage(); 242 | $innerMessage2 = new ParentMessage\InnerMessage(); 243 | 244 | $innerMessage1->setEnum(ParentMessage\InnerMessage\InnerMessageEnum::VALUE1()); 245 | $innerMessage2->setEnum(ParentMessage\InnerMessage\InnerMessageEnum::VALUE2()); 246 | 247 | $parentMessage->addInner($innerMessage1); 248 | $parentMessage->addInner($innerMessage2); 249 | $parentMessage->setEnum(ParentMessage\InnerEnum::VALUE1()); 250 | 251 | $expected = $this->getProtoContent('php_options.bin'); 252 | $actual = $parentMessage->toStream(); 253 | 254 | $this->assertEquals($expected, (string) $actual); 255 | $this->assertSerializedMessageSize($expected, $parentMessage); 256 | } 257 | 258 | public function testWriteTreeMessage() 259 | { 260 | $root = new Tree\Node(); 261 | $admin = new Tree\Node(); 262 | $fabio = new Tree\Node(); 263 | 264 | $root->setPath('/Users'); 265 | $fabio->setPath('/Users/fabio'); 266 | $admin->setPath('/Users/admin'); 267 | 268 | // avoid recursion 269 | $parent = clone $root; 270 | 271 | $admin->setParent($parent); 272 | $fabio->setParent($parent); 273 | 274 | $root->addChildren($fabio); 275 | $root->addChildren($admin); 276 | 277 | $expected = $this->getProtoContent('tree.bin'); 278 | $actual = $root->toStream(); 279 | 280 | $this->assertEquals($expected, (string) $actual); 281 | $this->assertSerializedMessageSize($expected, $root); 282 | } 283 | 284 | public function testWriteAnimalExtensionMessage() 285 | { 286 | $cat = new Extension\Cat(); 287 | $animal = new Extension\Animal(); 288 | 289 | $cat->setDeclawed(true); 290 | 291 | $animal->setType(Extension\Animal\Type::CAT()); 292 | $animal->extensions()->put(Extension\Cat::animal(), $cat); 293 | 294 | $expected = $this->getProtoContent('extension-animal-cat.bin'); 295 | $actual = $animal->toStream(); 296 | 297 | $this->assertEquals($expected, (string) $actual); 298 | $this->assertSerializedMessageSize($expected, $animal); 299 | } 300 | 301 | public function testWriteCommandExtensionMessage() 302 | { 303 | $version = new Extension\VersionCommand(); 304 | $command = new Extension\Command(); 305 | 306 | $version->setVersion(1); 307 | $version->setProtocol(Extension\VersionCommand\Protocol::V1()); 308 | 309 | $command->setType(Extension\Command\CommandType::VERSION()); 310 | $command->extensions()->put(Extension\Extension::verbose(), true); 311 | $command->extensions()->put(Extension\VersionCommand::cmd(), $version); 312 | 313 | $expected = $this->getProtoContent('extension-command-version.bin'); 314 | $actual = $command->toStream(); 315 | 316 | $this->assertEquals($expected, (string) $actual); 317 | $this->assertSerializedMessageSize($expected, $command); 318 | } 319 | 320 | public function testReadSimpleMessage() 321 | { 322 | $binary = $this->getProtoContent('simple.bin'); 323 | $simple = Simple::fromStream($binary); 324 | 325 | $this->assertInstanceOf(Simple::CLASS, $simple); 326 | $this->assertInstanceOf(Stream::CLASS, $simple->getBytes()); 327 | 328 | $this->assertInternalType('bool', $simple->getBool()); 329 | $this->assertInternalType('string', $simple->getString()); 330 | $this->assertInternalType('float', $simple->getFloat(), '', 0.0001); 331 | $this->assertInternalType('integer', $simple->getUint32()); 332 | $this->assertInternalType('integer', $simple->getInt32()); 333 | $this->assertInternalType('integer', $simple->getFixed32()); 334 | $this->assertInternalType('integer', $simple->getSint32()); 335 | $this->assertInternalType('integer', $simple->getSfixed32()); 336 | $this->assertInternalType('float', $simple->getDouble()); 337 | $this->assertInternalType('integer', $simple->getInt64()); 338 | $this->assertInternalType('integer', $simple->getUint64()); 339 | $this->assertInternalType('integer', $simple->getFixed64()); 340 | $this->assertInternalType('integer', $simple->getSint64()); 341 | $this->assertInternalType('integer', $simple->getSfixed64()); 342 | 343 | $this->assertEquals(true, $simple->getBool()); 344 | $this->assertEquals("bar", $simple->getBytes()); 345 | $this->assertEquals("foo", $simple->getString()); 346 | $this->assertEquals(12345.123, $simple->getFloat(), '', 0.0001); 347 | $this->assertEquals(123456789, $simple->getUint32()); 348 | $this->assertEquals(-123456789, $simple->getInt32()); 349 | $this->assertEquals(123456789, $simple->getFixed32()); 350 | $this->assertEquals(-123456789, $simple->getSint32()); 351 | $this->assertEquals(-123456789, $simple->getSfixed32()); 352 | $this->assertEquals(123456789.12345, $simple->getDouble()); 353 | $this->assertEquals(-123456789123456789, $simple->getInt64()); 354 | $this->assertEquals(123456789123456789, $simple->getUint64()); 355 | $this->assertEquals(123456789123456789, $simple->getFixed64()); 356 | $this->assertEquals(-123456789123456789, $simple->getSint64()); 357 | $this->assertEquals(-123456789123456789, $simple->getSfixed64()); 358 | } 359 | 360 | public function testReadRepeatedString() 361 | { 362 | $binary = $this->getProtoContent('repeated-string.bin'); 363 | $repeated = Repeated::fromStream($binary); 364 | 365 | $this->assertInstanceOf(Repeated::CLASS, $repeated); 366 | $this->assertInstanceOf(Collection::CLASS, $repeated->getStringList()); 367 | $this->assertEquals(['one', 'two', 'three'], $repeated->getStringList()->getArrayCopy()); 368 | } 369 | 370 | public function testReadRepeatedInt32() 371 | { 372 | $binary = $this->getProtoContent('repeated-int32.bin'); 373 | $repeated = Repeated::fromStream($binary); 374 | 375 | $this->assertInstanceOf(Repeated::CLASS, $repeated); 376 | $this->assertInstanceOf(Collection::CLASS, $repeated->getIntList()); 377 | $this->assertEquals([1, 2, 3], $repeated->getIntList()->getArrayCopy()); 378 | } 379 | 380 | public function testReadRepeatedNested() 381 | { 382 | $binary = $this->getProtoContent('repeated-nested.bin'); 383 | $repeated = Repeated::fromStream($binary); 384 | 385 | $this->assertInstanceOf(Repeated::CLASS, $repeated); 386 | $this->assertInstanceOf(Collection::CLASS, $repeated->getNestedList()); 387 | $this->assertCount(3, $repeated->getNestedList()); 388 | 389 | $this->assertInstanceOf(Repeated\Nested::CLASS, $repeated->getNestedList()[0]); 390 | $this->assertInstanceOf(Repeated\Nested::CLASS, $repeated->getNestedList()[1]); 391 | $this->assertInstanceOf(Repeated\Nested::CLASS, $repeated->getNestedList()[2]); 392 | 393 | $this->assertEquals(1, $repeated->getNestedList()[0]->getId()); 394 | $this->assertEquals(2, $repeated->getNestedList()[1]->getId()); 395 | $this->assertEquals(3, $repeated->getNestedList()[2]->getId()); 396 | } 397 | 398 | public function testReadRepeatedPacked() 399 | { 400 | $binary = $this->getProtoContent('repeated-packed.bin'); 401 | $repeated = Repeated::fromStream($binary); 402 | 403 | $this->assertInstanceOf(Repeated::CLASS, $repeated); 404 | $this->assertInstanceOf(Collection::CLASS, $repeated->getPackedList()); 405 | $this->assertEquals([1, 2, 3], $repeated->getPackedList()->getArrayCopy()); 406 | } 407 | 408 | public function testReadRepeatedPackedEnum() 409 | { 410 | $enumVal = [Repeated\Enum::FOO(), Repeated\Enum::BAR()]; 411 | $binary = $this->getProtoContent('repeated-packed-enum.bin'); 412 | $repeated = Repeated::fromStream($binary); 413 | 414 | $this->assertInstanceOf(Repeated::CLASS, $repeated); 415 | $this->assertInstanceOf(Collection::CLASS, $repeated->getPackedEnumList()); 416 | $this->assertEquals($enumVal, $repeated->getPackedEnumList()->getArrayCopy()); 417 | } 418 | 419 | public function testReadRepeatedBytes() 420 | { 421 | $binary = $this->getProtoContent('repeated-bytes.bin'); 422 | $repeated = Repeated::fromStream($binary); 423 | 424 | $this->assertInstanceOf(Repeated::CLASS, $repeated); 425 | $this->assertInstanceOf(Collection::CLASS, $repeated->getBytesList()); 426 | $this->assertCount(3, $repeated->getBytesList()); 427 | 428 | $this->assertInstanceOf('Protobuf\Stream', $repeated->getBytesList()[0]); 429 | $this->assertInstanceOf('Protobuf\Stream', $repeated->getBytesList()[1]); 430 | $this->assertInstanceOf('Protobuf\Stream', $repeated->getBytesList()[2]); 431 | 432 | $this->assertEquals('bin1', $repeated->getBytesList()[0]); 433 | $this->assertEquals('bin2', $repeated->getBytesList()[1]); 434 | $this->assertEquals('bin3', $repeated->getBytesList()[2]); 435 | } 436 | 437 | public function testReadRepeatedEnum() 438 | { 439 | $binary = $this->getProtoContent('repeated-enum.bin'); 440 | $repeated = Repeated::fromStream($binary); 441 | 442 | $this->assertInstanceOf(Repeated::CLASS, $repeated); 443 | $this->assertInstanceOf(Collection::CLASS, $repeated->getEnumList()); 444 | $this->assertCount(2, $repeated->getEnumList()); 445 | 446 | $this->assertInstanceOf('Protobuf\Enum', $repeated->getEnumList()[0]); 447 | $this->assertInstanceOf('Protobuf\Enum', $repeated->getEnumList()[1]); 448 | 449 | $this->assertSame(Repeated\Enum::FOO(), $repeated->getEnumList()[0]); 450 | $this->assertSame(Repeated\Enum::BAR(), $repeated->getEnumList()[1]); 451 | } 452 | 453 | public function testReadComplexMessage() 454 | { 455 | $binary = $this->getProtoContent('addressbook.bin'); 456 | $complex = AddressBook::fromStream($binary); 457 | 458 | $this->assertInstanceOf(AddressBook::CLASS, $complex); 459 | $this->assertCount(2, $complex->getPersonList()); 460 | 461 | $person1 = $complex->getPersonList()[0]; 462 | $person2 = $complex->getPersonList()[1]; 463 | 464 | $this->assertInstanceOf(Person::CLASS, $person1); 465 | $this->assertInstanceOf(Person::CLASS, $person2); 466 | 467 | $this->assertEquals($person1->getId(), 2051); 468 | $this->assertEquals($person1->getName(), 'John Doe'); 469 | 470 | $this->assertEquals($person2->getId(), 23); 471 | $this->assertEquals($person2->getName(), 'Iván Montes'); 472 | 473 | $this->assertCount(2, $person1->getPhoneList()); 474 | $this->assertCount(1, $person2->getPhoneList()); 475 | 476 | $this->assertEquals($person1->getPhoneList()[0]->getNumber(), '1231231212'); 477 | $this->assertEquals($person1->getPhoneList()[0]->getType(), PhoneType::HOME()); 478 | 479 | $this->assertEquals($person1->getPhoneList()[1]->getNumber(), '55512321312'); 480 | $this->assertEquals($person1->getPhoneList()[1]->getType(), PhoneType::MOBILE()); 481 | 482 | $this->assertEquals($person2->getPhoneList()[0]->getNumber(), '3493123123'); 483 | $this->assertEquals($person2->getPhoneList()[0]->getType(), PhoneType::WORK()); 484 | } 485 | 486 | public function testReadPhpOptionsMessage() 487 | { 488 | $binary = $this->getProtoContent('php_options.bin'); 489 | $message = ParentMessage::fromStream($binary); 490 | 491 | $this->assertInstanceOf(ParentMessage::CLASS, $message); 492 | $this->assertCount(2, $message->getInnerList()); 493 | $this->assertSame(ParentMessage\InnerEnum::VALUE1(), $message->getEnum()); 494 | 495 | $inner1 = $message->getInnerList()[0]; 496 | $inner2 = $message->getInnerList()[1]; 497 | 498 | $this->assertInstanceOf(ParentMessage\InnerMessage::CLASS, $inner1); 499 | $this->assertInstanceOf(ParentMessage\InnerMessage::CLASS, $inner2); 500 | 501 | $this->assertSame(ParentMessage\InnerMessage\InnerMessageEnum::VALUE1(), $inner1->getEnum()); 502 | $this->assertSame(ParentMessage\InnerMessage\InnerMessageEnum::VALUE2(), $inner2->getEnum()); 503 | } 504 | 505 | public function testReadTreeMessage() 506 | { 507 | $binary = $this->getProtoContent('tree.bin'); 508 | $root = Tree\Node::fromStream($binary); 509 | 510 | $this->assertInstanceOf(Tree\Node::CLASS, $root); 511 | $this->assertCount(2, $root->getChildrenList()); 512 | $this->assertEquals($root->getPath(), '/Users'); 513 | 514 | $node1 = $root->getChildrenList()[0]; 515 | $node2 = $root->getChildrenList()[1]; 516 | 517 | $this->assertInstanceOf(Tree\Node::CLASS, $node1); 518 | $this->assertInstanceOf(Tree\Node::CLASS, $node2); 519 | 520 | $this->assertEquals('/Users/fabio', $node1->getPath()); 521 | $this->assertEquals('/Users/admin', $node2->getPath()); 522 | 523 | $this->assertInstanceOf(Tree\Node::CLASS, $node1->getParent()); 524 | $this->assertInstanceOf(Tree\Node::CLASS, $node2->getParent()); 525 | 526 | $this->assertEquals('/Users', $node1->getParent()->getPath()); 527 | $this->assertEquals('/Users', $node2->getParent()->getPath()); 528 | } 529 | 530 | public function testReadExtensionAnimalMessage() 531 | { 532 | Extension\Extension::registerAllExtensions($this->config->getExtensionRegistry()); 533 | 534 | $binary = $this->getProtoContent('extension-animal-cat.bin'); 535 | $animal = Extension\Animal::fromStream($binary, $this->config); 536 | 537 | $this->assertInstanceOf(Extension\Animal::CLASS, $animal); 538 | $this->assertInstanceOf(Collection::CLASS, $animal->extensions()); 539 | $this->assertEquals(Extension\Animal\Type::CAT(), $animal->getType()); 540 | 541 | $extensions = $animal->extensions(); 542 | $cat = $extensions->get(Extension\Cat::animal()); 543 | 544 | $this->assertInstanceOf(Extension\Cat::CLASS, $cat); 545 | $this->assertTrue($cat->getDeclawed()); 546 | } 547 | 548 | public function testReadExtensionCommandMessage() 549 | { 550 | Extension\Extension::registerAllExtensions($this->config->getExtensionRegistry()); 551 | 552 | $binary = $this->getProtoContent('extension-command-version.bin'); 553 | $command = Extension\Command::fromStream($binary, $this->config); 554 | 555 | $this->assertInstanceOf(Extension\Command::CLASS, $command); 556 | $this->assertInstanceOf(Collection::CLASS, $command->extensions()); 557 | $this->assertEquals(Extension\Command\CommandType::VERSION(), $command->getType()); 558 | 559 | $extensions = $command->extensions(); 560 | $verbose = $extensions->get(Extension\Extension::verbose()); 561 | $version = $extensions->get(Extension\VersionCommand::cmd()); 562 | 563 | $this->assertTrue($verbose); 564 | $this->assertInstanceOf(Extension\VersionCommand::CLASS, $version); 565 | $this->assertEquals(1, $version->getVersion()); 566 | $this->assertSame(Extension\VersionCommand\Protocol::V1(), $version->getProtocol()); 567 | } 568 | 569 | public function testUnknownFieldSet() 570 | { 571 | $binary = $this->getProtoContent('unknown.bin'); 572 | $unrecognized = Unrecognized::fromStream(Stream::wrap($binary)); 573 | 574 | $this->assertInstanceOf(Unrecognized::CLASS, $unrecognized); 575 | $this->assertInstanceOf('Protobuf\UnknownFieldSet', $unrecognized->unknownFieldSet()); 576 | $this->assertCount(15, $unrecognized->unknownFieldSet()); 577 | 578 | $values = $unrecognized->unknownFieldSet(); 579 | 580 | $this->assertInstanceOf('Protobuf\Unknown', $values[1]); 581 | $this->assertInstanceOf('Protobuf\Unknown', $values[2]); 582 | $this->assertInstanceOf('Protobuf\Unknown', $values[3]); 583 | $this->assertInstanceOf('Protobuf\Unknown', $values[4]); 584 | $this->assertInstanceOf('Protobuf\Unknown', $values[5]); 585 | $this->assertInstanceOf('Protobuf\Unknown', $values[6]); 586 | $this->assertInstanceOf('Protobuf\Unknown', $values[7]); 587 | $this->assertInstanceOf('Protobuf\Unknown', $values[8]); 588 | $this->assertInstanceOf('Protobuf\Unknown', $values[9]); 589 | $this->assertInstanceOf('Protobuf\Unknown', $values[12]); 590 | $this->assertInstanceOf('Protobuf\Unknown', $values[13]); 591 | $this->assertInstanceOf('Protobuf\Unknown', $values[15]); 592 | $this->assertInstanceOf('Protobuf\Unknown', $values[16]); 593 | $this->assertInstanceOf('Protobuf\Unknown', $values[17]); 594 | $this->assertInstanceOf('Protobuf\Unknown', $values[18]); 595 | 596 | $this->assertEquals(4728057454355442093, $values[1]->value); 597 | $this->assertEquals(1178657918, $values[2]->value); 598 | $this->assertEquals(-123456789123456789, $values[3]->value); 599 | $this->assertEquals(123456789123456789, $values[4]->value); 600 | $this->assertEquals(-123456789, $values[5]->value); 601 | $this->assertEquals(123456789123456789, $values[6]->value); 602 | $this->assertEquals(123456789, $values[7]->value); 603 | $this->assertEquals(1, $values[8]->value); 604 | $this->assertEquals("foo", $values[9]->value); 605 | $this->assertEquals("bar", $values[12]->value); 606 | $this->assertEquals(123456789, $values[13]->value); 607 | $this->assertEquals(4171510507, $values[15]->value); 608 | $this->assertEquals(-123456789123456789, $values[16]->value); 609 | $this->assertEquals(246913577, $values[17]->value); 610 | $this->assertEquals(246913578246913577, $values[18]->value); 611 | } 612 | } 613 | --------------------------------------------------------------------------------