public function testClassWithEmbeddedMessagePropertyToProtobuf()
 {
     $s = "the answer to life the universe and everything";
     $embedded = chr(1 << 3 | WireTypeEnum::toBinaryWireType(WireTypeEnum::STRING)) . chr(strlen($s)) . $s;
     $this->assertEquals(chr(1 << 3 | WireTypeEnum::toBinaryWireType(WireTypeEnum::STRING)) . chr(strlen($embedded)) . $embedded, ClassWithEmbeddedMessagePropertyMeta::toProtobuf((new ClassWithEmbeddedMessageProperty())->setX((new ClassWithEmbeddedMessageProperty\Embedded())->setX($s))));
 }
Beispiel #2
0
 public function onGenerate(AbstractMetaSpec $spec, MetaSpecMatcher $matcher, Type $type, ClassType $class)
 {
     $ns = $class->getNamespace();
     $ns->addUse("Skrz\\Meta\\Protobuf\\ProtobufMetaInterface");
     $ns->addUse($type->getName(), null, $typeAlias);
     $ns->addUse("Skrz\\Meta\\Protobuf\\Binary", null, $binary);
     $ns->addUse("Skrz\\Meta\\Protobuf\\ProtobufException", null, $protobufExceptionAlias);
     $class->addImplement("Skrz\\Meta\\Protobuf\\ProtobufMetaInterface");
     foreach ($type->getProperties() as $property) {
         if ($property->hasAnnotation("Skrz\\Meta\\Transient")) {
             continue;
         }
         /** @var ProtobufField $field */
         $field = $property->getAnnotation("Skrz\\Meta\\Protobuf\\ProtobufField");
         $class->addConst(strtoupper(trim(preg_replace("/([A-Z])/", "_\$1", $property->getName() . "ProtobufField"), "_")), $field->number);
     }
     $fromProtobuf = $class->addMethod("fromProtobuf");
     $fromProtobuf->setStatic(true);
     $fromProtobuf->addParameter("input");
     $fromProtobuf->addParameter("object", null)->setOptional(true);
     $fromProtobuf->addParameter("start", 0)->setReference(true)->setOptional(true);
     $fromProtobuf->addParameter("end", null)->setOptional(true);
     $fromProtobuf->addComment("Creates \\{$type->getName()} object from serialized Protocol Buffers message.")->addComment("")->addComment("@param string \$input")->addComment("@param {$typeAlias} \$object")->addComment("@param int \$start")->addComment("@param int \$end")->addComment("")->addComment("@throws \\Exception")->addComment("")->addComment("@return {$typeAlias}");
     $fromProtobuf->addBody("if (\$object === null) {")->addBody("\t\$object = new {$typeAlias}();")->addBody("}")->addBody("")->addBody("if (\$end === null) {")->addBody("\t\$end = strlen(\$input);")->addBody("}")->addBody("");
     $fromProtobuf->addBody("while (\$start < \$end) {");
     $fromProtobuf->addBody("\t\$tag = {$binary}::decodeVarint(\$input, \$start);");
     $fromProtobuf->addBody("\t\$wireType = \$tag & 0x7;");
     $fromProtobuf->addBody("\t\$number = \$tag >> 3;");
     $fromProtobuf->addBody("\tswitch (\$number) {");
     foreach ($type->getProperties() as $property) {
         if ($property->hasAnnotation("Skrz\\Meta\\Transient")) {
             continue;
         }
         $propertyType = $property->getType();
         $baseType = $propertyType;
         if ($baseType instanceof ArrayType) {
             $baseType = $baseType->getBaseType();
         }
         /** @var ProtobufField $field */
         $field = $property->getAnnotation("Skrz\\Meta\\Protobuf\\ProtobufField");
         $fromProtobuf->addBody("\t\tcase {$field->number}:");
         if ($field->packed) {
             $binaryWireType = WireTypeEnum::toBinaryWireType(WireTypeEnum::STRING);
         } else {
             $binaryWireType = WireTypeEnum::toBinaryWireType($field->wireType);
         }
         $fromProtobuf->addBody("\t\t\tif (\$wireType !== {$binaryWireType}) {");
         $fromProtobuf->addBody("\t\t\t\tthrow new {$protobufExceptionAlias}('Unexpected wire type ' . \$wireType . ', expected {$binaryWireType}.', \$number);");
         $fromProtobuf->addBody("\t\t\t}");
         $propertyLhs = "\$object->{$property->getName()}";
         if ($propertyType->isArray()) {
             $fromProtobuf->addBody("\t\t\tif (!(isset({$propertyLhs}) && is_array({$propertyLhs}))) {");
             $fromProtobuf->addBody("\t\t\t\t{$propertyLhs} = array();");
             $fromProtobuf->addBody("\t\t\t}");
             $propertyLhs .= "[]";
         }
         if ($field->packed) {
             $fromProtobuf->addBody("\t\t\t\$packedLength = {$binary}::decodeVarint(\$input, \$start);")->addBody("\t\t\t\$expectedPacked = \$start + \$packedLength;")->addBody("\t\t\tif (\$expectedPacked > \$end) {")->addBody("\t\t\t\tthrow new {$protobufExceptionAlias}('Not enough data.');")->addBody("\t\t\t}")->addBody("\t\t\twhile (\$start < \$expectedPacked) {");
             $indent = "\t\t\t\t";
         } else {
             $indent = "\t\t\t";
         }
         switch ($field->wireType) {
             case WireTypeEnum::VARINT:
                 $fromProtobuf->addBody("{$indent}{$propertyLhs} = " . ($baseType instanceof BoolType ? "(bool)" : "") . "{$binary}::decodeVarint(\$input, \$start);");
                 break;
             case WireTypeEnum::ZIGZAG:
                 $fromProtobuf->addBody("{$indent}{$propertyLhs} = {$binary}::decodeZigzag(\$input, \$start);");
                 break;
             case WireTypeEnum::FIXED64:
                 $fromProtobuf->addBody("{$indent}\$expectedStart = \$start + 8;")->addBody("{$indent}if (\$expectedStart > \$end) {")->addBody("{$indent}\tthrow new {$protobufExceptionAlias}('Not enough data.');")->addBody("{$indent}}");
                 if ($baseType instanceof FloatType) {
                     $fromProtobuf->addBody("{$indent}{$propertyLhs} = {$binary}::decodeDouble(\$input, \$start);");
                 } elseif ($baseType instanceof IntType && $field->unsigned) {
                     $fromProtobuf->addBody("{$indent}{$propertyLhs} = {$binary}::decodeUint64(\$input, \$start);");
                 } elseif ($baseType instanceof IntType && !$field->unsigned) {
                     $fromProtobuf->addBody("{$indent}{$propertyLhs} = {$binary}::decodeInt64(\$input, \$start);");
                 } else {
                     throw new MetaException("Property {$type->getName()}::\${$property->getName()} has wire type '{$field->wireType}' and base type {$baseType}. " . "'{$field->wireType}' supports only float or int base types.");
                 }
                 $fromProtobuf->addBody("{$indent}if (\$start !== \$expectedStart) {")->addBody("{$indent}\tthrow new {$protobufExceptionAlias}('Unexpected start. Expected ' . \$expectedStart . ', got ' . \$start . '.', \$number);")->addBody("{$indent}}");
                 break;
             case WireTypeEnum::FIXED32:
                 $fromProtobuf->addBody("{$indent}\$expectedStart = \$start + 4;")->addBody("{$indent}if (\$expectedStart > \$end) {")->addBody("{$indent}\tthrow new {$protobufExceptionAlias}('Not enough data.');")->addBody("{$indent}}");
                 if ($baseType instanceof FloatType) {
                     $fromProtobuf->addBody("{$indent}{$propertyLhs} = {$binary}::decodeFloat(\$input, \$start);");
                 } elseif ($baseType instanceof IntType && $field->unsigned) {
                     $fromProtobuf->addBody("{$indent}{$propertyLhs} = {$binary}::decodeUint32(\$input, \$start);");
                 } elseif ($baseType instanceof IntType && !$field->unsigned) {
                     $fromProtobuf->addBody("{$indent}{$propertyLhs} = {$binary}::decodeInt32(\$input, \$start);");
                 } else {
                     throw new MetaException("Property {$type->getName()}::\${$property->getName()} has wire type '{$field->wireType}' and base type {$baseType}. " . "'{$field->wireType}' supports only float or int base types.");
                 }
                 $fromProtobuf->addBody("{$indent}if (\$start !== \$expectedStart) {")->addBody("{$indent}\tthrow new {$protobufExceptionAlias}('Unexpected start. Expected ' . \$expectedStart . ', got ' . \$start . '.', \$number);")->addBody("{$indent}}");
                 break;
             case WireTypeEnum::STRING:
                 $fromProtobuf->addBody("{$indent}\$length = {$binary}::decodeVarint(\$input, \$start);")->addBody("{$indent}\$expectedStart = \$start + \$length;")->addBody("{$indent}if (\$expectedStart > \$end) {")->addBody("{$indent}\tthrow new {$protobufExceptionAlias}('Not enough data.');")->addBody("{$indent}}");
                 if ($baseType instanceof StringType) {
                     $fromProtobuf->addBody("{$indent}{$propertyLhs} = substr(\$input, \$start, \$length);");
                     $fromProtobuf->addBody("{$indent}\$start += \$length;");
                 } elseif ($baseType instanceof Type) {
                     $propertyTypeMetaClassName = $spec->createMetaClassName($baseType);
                     $ns->addUse($propertyTypeMetaClassName, null, $propertyTypeMetaClassNameAlias);
                     $fromProtobuf->addBody("{$indent}{$propertyLhs} = {$propertyTypeMetaClassNameAlias}::fromProtobuf(\$input, null, \$start, \$start + \$length);");
                 } else {
                     throw new MetaException("Property {$type->getName()}::\${$property->getName()} has wire type '{$field->wireType}' and base type {$baseType}. " . "'{$field->wireType}' supports only string or object types.");
                 }
                 $fromProtobuf->addBody("{$indent}if (\$start !== \$expectedStart) {")->addBody("{$indent}\tthrow new {$protobufExceptionAlias}('Unexpected start. Expected ' . \$expectedStart . ', got ' . \$start . '.', \$number);")->addBody("{$indent}}");
                 break;
             default:
                 throw new \InvalidArgumentException("Unhandled field wire type '{$field->wireType}'.");
         }
         if ($field->packed) {
             $fromProtobuf->addBody("\t\t\t}")->addBody("\t\t\tif (\$start !== \$expectedPacked) {")->addBody("\t\t\t\tthrow new {$protobufExceptionAlias}('Unexpected start. Expected ' . \$expectedPacked . ', got ' . \$start . '.', \$number);")->addBody("\t\t\t}");
         }
         $fromProtobuf->addBody("\t\t\tbreak;");
     }
     $fromProtobuf->addBody("\t\tdefault:")->addBody("\t\t\tswitch (\$wireType) {")->addBody("\t\t\t\tcase " . WireTypeEnum::toBinaryWireType(WireTypeEnum::VARINT) . ":")->addBody("\t\t\t\t\t{$binary}::decodeVarint(\$input, \$start);")->addBody("\t\t\t\t\tbreak;")->addBody("\t\t\t\tcase " . WireTypeEnum::toBinaryWireType(WireTypeEnum::FIXED64) . ":")->addBody("\t\t\t\t\t\$start += 8;")->addBody("\t\t\t\t\tbreak;")->addBody("\t\t\t\tcase " . WireTypeEnum::toBinaryWireType(WireTypeEnum::STRING) . ":")->addBody("\t\t\t\t\t\$start += {$binary}::decodeVarint(\$input, \$start);")->addBody("\t\t\t\t\tbreak;")->addBody("\t\t\t\tcase " . WireTypeEnum::toBinaryWireType(WireTypeEnum::FIXED32) . ":")->addBody("\t\t\t\t\t\$start += 4;")->addBody("\t\t\t\t\tbreak;")->addBody("\t\t\t\tdefault:")->addBody("\t\t\t\t\tthrow new {$protobufExceptionAlias}('Unexpected wire type ' . \$wireType . '.', \$number);")->addBody("\t\t\t}");
     $fromProtobuf->addBody("\t}");
     $fromProtobuf->addBody("}");
     $fromProtobuf->addBody("")->addBody("return \$object;");
     $toProtobuf = $class->addMethod("toProtobuf");
     $toProtobuf->setStatic(true);
     $toProtobuf->addParameter("object");
     $toProtobuf->addParameter("filter", null);
     $toProtobuf->addComment("Serialized \\{$type->getName()} to Protocol Buffers message.")->addComment("")->addComment("@param {$typeAlias} \$object")->addComment("@param array \$filter")->addComment("")->addComment("@throws \\Exception")->addComment("")->addComment("@return string");
     $toProtobuf->addBody("\$output = '';")->addBody("");
     foreach ($type->getProperties() as $property) {
         if ($property->hasAnnotation("Skrz\\Meta\\Transient")) {
             continue;
         }
         $propertyType = $property->getType();
         $baseType = $propertyType;
         if ($baseType instanceof ArrayType) {
             $baseType = $baseType->getBaseType();
         }
         /** @var ProtobufField $field */
         $field = $property->getAnnotation("Skrz\\Meta\\Protobuf\\ProtobufField");
         $toProtobuf->addBody("if (isset(\$object->{$property->getName()}) && (\$filter === null || isset(\$filter[" . var_export($property->getName(), true) . "]))) {");
         $var = "\$object->{$property->getName()}";
         $output = "\$output";
         $indent = "\t";
         if ($field->packed) {
             $toProtobuf->addBody("\t\$packedBuffer = '';")->addBody("\t\$output .= \"" . implode("", array_map(function ($s) {
                 return "\\x" . $s;
             }, str_split(bin2hex(Binary::encodeVarint($field->number << 3 | WireTypeEnum::toBinaryWireType(WireTypeEnum::STRING))), 2))) . "\";");
             $output = "\$packedBuffer";
         }
         if ($propertyType->isArray()) {
             $toProtobuf->addBody("\tforeach ({$var} instanceof \\Traversable ? {$var} : (array){$var} as \$k => \$v) {");
             $var = "\$v";
             $indent .= "\t";
         }
         if (!$field->packed) {
             $toProtobuf->addBody("{$indent}{$output} .= \"" . implode("", array_map(function ($s) {
                 return "\\x" . $s;
             }, str_split(bin2hex(Binary::encodeVarint($field->number << 3 | WireTypeEnum::toBinaryWireType($field->wireType))), 2))) . "\";");
         }
         switch ($field->wireType) {
             case WireTypeEnum::VARINT:
                 $toProtobuf->addBody("{$indent}{$output} .= {$binary}::encodeVarint(" . ($baseType instanceof BoolType ? "(int)" : "") . "{$var});");
                 break;
             case WireTypeEnum::ZIGZAG:
                 $toProtobuf->addBody("{$indent}{$output} .= {$binary}::encodeZigzag({$var});");
                 break;
             case WireTypeEnum::FIXED64:
                 if ($baseType instanceof FloatType) {
                     $toProtobuf->addBody("{$indent}{$output} .= {$binary}::encodeDouble({$var});");
                 } elseif ($baseType instanceof IntType && $field->unsigned) {
                     $toProtobuf->addBody("{$indent}{$output} .= {$binary}::encodeUint64({$var});");
                 } elseif ($baseType instanceof IntType && !$field->unsigned) {
                     $toProtobuf->addBody("{$indent}{$output} .= {$binary}::encodeInt64({$var});");
                 } else {
                     throw new MetaException("Property {$type->getName()}::\${$property->getName()} has wire type '{$field->wireType}' and base type {$baseType}. " . "'{$field->wireType}' supports only float or int base types.");
                 }
                 break;
             case WireTypeEnum::STRING:
                 if ($baseType instanceof StringType) {
                     $toProtobuf->addBody("{$indent}{$output} .= {$binary}::encodeVarint(strlen({$var}));");
                     $toProtobuf->addBody("{$indent}{$output} .= {$var};");
                 } elseif ($baseType instanceof Type) {
                     $propertyTypeMetaClassName = $spec->createMetaClassName($baseType);
                     $ns->addUse($propertyTypeMetaClassName, null, $propertyTypeMetaClassNameAlias);
                     $toProtobuf->addBody("{$indent}\$buffer = {$propertyTypeMetaClassNameAlias}::toProtobuf({$var}, \$filter === null ? null : \$filter[" . var_export($property->getName(), true) . "]);");
                     $toProtobuf->addBody("{$indent}{$output} .= {$binary}::encodeVarint(strlen(\$buffer));");
                     $toProtobuf->addBody("{$indent}{$output} .= \$buffer;");
                 } else {
                     throw new MetaException("Property {$type->getName()}::\${$property->getName()} has wire type '{$field->wireType}' and base type {$baseType}. " . "'{$field->wireType}' supports only string or object types.");
                 }
                 break;
             case WireTypeEnum::FIXED32:
                 if ($baseType instanceof FloatType) {
                     $toProtobuf->addBody("{$indent}{$output} .= {$binary}::encodeFloat({$var});");
                 } elseif ($baseType instanceof IntType && $field->unsigned) {
                     $toProtobuf->addBody("{$indent}{$output} .= {$binary}::encodeUint32({$var});");
                 } elseif ($baseType instanceof IntType && !$field->unsigned) {
                     $toProtobuf->addBody("{$indent}{$output} .= {$binary}::encodeInt32({$var});");
                 } else {
                     throw new MetaException("Property {$type->getName()}::\${$property->getName()} has wire type '{$field->wireType}' and base type {$baseType}. " . "'{$field->wireType}' supports only float or int base types.");
                 }
                 break;
         }
         if ($propertyType->isArray()) {
             $toProtobuf->addBody("\t}");
         }
         if ($field->packed) {
             $toProtobuf->addBody("\t\$output .= {$binary}::encodeVarint(strlen(\$packedBuffer));");
             $toProtobuf->addBody("\t\$output .= \$packedBuffer;");
         }
         $toProtobuf->addBody("}")->addBody("");
     }
     $toProtobuf->addBody("return \$output;");
 }