// Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include #include #include #include #include #include #include using google::protobuf::internal::scoped_ptr; const std::string kDescriptorFile = "google/protobuf/descriptor.proto"; const std::string kEmptyFile = "google/protobuf/empty.proto"; const std::string kEmptyMetadataFile = "GPBMetadata/Google/Protobuf/GPBEmpty.php"; const std::string kDescriptorMetadataFile = "GPBMetadata/Google/Protobuf/Internal/Descriptor.php"; const std::string kDescriptorDirName = "Google/Protobuf/Internal"; const std::string kDescriptorPackageName = "Google\\Protobuf\\Internal"; namespace google { namespace protobuf { namespace compiler { namespace php { // Forward decls. std::string PhpName(const std::string& full_name, bool is_descriptor); std::string DefaultForField(FieldDescriptor* field); std::string IntToString(int32 value); std::string FilenameToClassname(const string& filename); std::string GeneratedMetadataFileName(const std::string& proto_file, bool is_descriptor); std::string LabelForField(FieldDescriptor* field); std::string TypeName(FieldDescriptor* field); std::string UnderscoresToCamelCase(const string& name, bool cap_first_letter); std::string EscapeDollor(const string& to_escape); std::string BinaryToHex(const string& binary); void Indent(io::Printer* printer); void Outdent(io::Printer* printer); void GenerateMessageDocComment(io::Printer* printer, const Descriptor* message); void GenerateFieldDocComment(io::Printer* printer, const FieldDescriptor* field); void GenerateEnumDocComment(io::Printer* printer, const EnumDescriptor* enum_); void GenerateEnumValueDocComment(io::Printer* printer, const EnumValueDescriptor* value); std::string RenameEmpty(const std::string& name) { if (name == "Empty") { return "GPBEmpty"; } else { return name; } } std::string MessagePrefix(const Descriptor* message) { // Empty cannot be php class name. if (message->name() == "Empty" && message->file()->package() == "google.protobuf") { return "GPB"; } else { return (message->file()->options()).php_class_prefix(); } } std::string MessageName(const Descriptor* message, bool is_descriptor) { string message_name = message->name(); const Descriptor* descriptor = message->containing_type(); while (descriptor != NULL) { message_name = descriptor->name() + '_' + message_name; descriptor = descriptor->containing_type(); } message_name = MessagePrefix(message) + message_name; if (message->file()->package() == "") { return message_name; } else { return PhpName(message->file()->package(), is_descriptor) + '\\' + message_name; } } std::string MessageFullName(const Descriptor* message, bool is_descriptor) { if (is_descriptor) { return StringReplace(message->full_name(), "google.protobuf", "google.protobuf.internal", false); } else { return message->full_name(); } } std::string EnumFullName(const EnumDescriptor* envm, bool is_descriptor) { if (is_descriptor) { return StringReplace(envm->full_name(), "google.protobuf", "google.protobuf.internal", false); } else { return envm->full_name(); } } std::string EnumClassName(const EnumDescriptor* envm) { string enum_class_name = envm->name(); const Descriptor* descriptor = envm->containing_type(); while (descriptor != NULL) { enum_class_name = descriptor->name() + '_' + enum_class_name; descriptor = descriptor->containing_type(); } return enum_class_name; } std::string EnumName(const EnumDescriptor* envm, bool is_descriptor) { string enum_name = EnumClassName(envm); return PhpName(envm->file()->package(), is_descriptor) + '\\' + enum_name; } std::string PhpName(const std::string& full_name, bool is_descriptor) { if (is_descriptor) { return kDescriptorPackageName; } std::string result; bool cap_next_letter = true; for (int i = 0; i < full_name.size(); i++) { if ('a' <= full_name[i] && full_name[i] <= 'z' && cap_next_letter) { result += full_name[i] + ('A' - 'a'); cap_next_letter = false; } else if (full_name[i] == '.') { result += '\\'; cap_next_letter = true; } else { result += full_name[i]; cap_next_letter = false; } } return result; } std::string DefaultForField(const FieldDescriptor* field) { switch (field->type()) { case FieldDescriptor::TYPE_INT32: case FieldDescriptor::TYPE_INT64: case FieldDescriptor::TYPE_UINT32: case FieldDescriptor::TYPE_UINT64: case FieldDescriptor::TYPE_SINT32: case FieldDescriptor::TYPE_SINT64: case FieldDescriptor::TYPE_FIXED32: case FieldDescriptor::TYPE_FIXED64: case FieldDescriptor::TYPE_SFIXED32: case FieldDescriptor::TYPE_SFIXED64: case FieldDescriptor::TYPE_ENUM: return "0"; case FieldDescriptor::TYPE_DOUBLE: case FieldDescriptor::TYPE_FLOAT: return "0.0"; case FieldDescriptor::TYPE_BOOL: return "false"; case FieldDescriptor::TYPE_STRING: case FieldDescriptor::TYPE_BYTES: return "''"; case FieldDescriptor::TYPE_MESSAGE: case FieldDescriptor::TYPE_GROUP: return "null"; default: assert(false); return ""; } } std::string GeneratedMetadataFileName(const std::string& proto_file, bool is_descriptor) { int start_index = 0; int first_index = proto_file.find_first_of("/", start_index); std::string result = "GPBMetadata/"; if (proto_file == kEmptyFile) { return kEmptyMetadataFile; } if (is_descriptor) { return kDescriptorMetadataFile; } // Append directory name. std::string file_no_suffix; int lastindex = proto_file.find_last_of("."); if (proto_file == kEmptyFile) { return kEmptyMetadataFile; } else { file_no_suffix = proto_file.substr(0, lastindex); } while (first_index != string::npos) { result += UnderscoresToCamelCase( file_no_suffix.substr(start_index, first_index - start_index), true); result += "/"; start_index = first_index + 1; first_index = file_no_suffix.find_first_of("/", start_index); } // Append file name. result += RenameEmpty(UnderscoresToCamelCase( file_no_suffix.substr(start_index, first_index - start_index), true)); return result += ".php"; } std::string GeneratedMessageFileName(const Descriptor* message, bool is_descriptor) { std::string result = MessageName(message, is_descriptor); for (int i = 0; i < result.size(); i++) { if (result[i] == '\\') { result[i] = '/'; } } return result + ".php"; } std::string GeneratedEnumFileName(const EnumDescriptor* en, bool is_descriptor) { std::string result = EnumName(en, is_descriptor); for (int i = 0; i < result.size(); i++) { if (result[i] == '\\') { result[i] = '/'; } } return result + ".php"; } std::string IntToString(int32 value) { std::ostringstream os; os << value; return os.str(); } std::string LabelForField(const FieldDescriptor* field) { switch (field->label()) { case FieldDescriptor::LABEL_OPTIONAL: return "optional"; case FieldDescriptor::LABEL_REQUIRED: return "required"; case FieldDescriptor::LABEL_REPEATED: return "repeated"; default: assert(false); return ""; } } std::string TypeName(const FieldDescriptor* field) { switch (field->type()) { case FieldDescriptor::TYPE_INT32: return "int32"; case FieldDescriptor::TYPE_INT64: return "int64"; case FieldDescriptor::TYPE_UINT32: return "uint32"; case FieldDescriptor::TYPE_UINT64: return "uint64"; case FieldDescriptor::TYPE_SINT32: return "sint32"; case FieldDescriptor::TYPE_SINT64: return "sint64"; case FieldDescriptor::TYPE_FIXED32: return "fixed32"; case FieldDescriptor::TYPE_FIXED64: return "fixed64"; case FieldDescriptor::TYPE_SFIXED32: return "sfixed32"; case FieldDescriptor::TYPE_SFIXED64: return "sfixed64"; case FieldDescriptor::TYPE_DOUBLE: return "double"; case FieldDescriptor::TYPE_FLOAT: return "float"; case FieldDescriptor::TYPE_BOOL: return "bool"; case FieldDescriptor::TYPE_ENUM: return "enum"; case FieldDescriptor::TYPE_STRING: return "string"; case FieldDescriptor::TYPE_BYTES: return "bytes"; case FieldDescriptor::TYPE_MESSAGE: return "message"; case FieldDescriptor::TYPE_GROUP: return "group"; default: assert(false); return ""; } } std::string EnumOrMessageSuffix( const FieldDescriptor* field, bool is_descriptor) { if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { return ", '" + MessageFullName(field->message_type(), is_descriptor) + "'"; } if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) { return ", '" + EnumFullName(field->enum_type(), is_descriptor) + "'"; } return ""; } // Converts a name to camel-case. If cap_first_letter is true, capitalize the // first letter. std::string UnderscoresToCamelCase(const string& input, bool cap_first_letter) { std::string result; for (int i = 0; i < input.size(); i++) { if ('a' <= input[i] && input[i] <= 'z') { if (cap_first_letter) { result += input[i] + ('A' - 'a'); } else { result += input[i]; } cap_first_letter = false; } else if ('A' <= input[i] && input[i] <= 'Z') { if (i == 0 && !cap_first_letter) { // Force first letter to lower-case unless explicitly told to // capitalize it. result += input[i] + ('a' - 'A'); } else { // Capital letters after the first are left as-is. result += input[i]; } cap_first_letter = false; } else if ('0' <= input[i] && input[i] <= '9') { result += input[i]; cap_first_letter = true; } else { cap_first_letter = true; } } // Add a trailing "_" if the name should be altered. if (input[input.size() - 1] == '#') { result += '_'; } return result; } std::string EscapeDollor(const string& to_escape) { return StringReplace(to_escape, "$", "\\$", true); } std::string BinaryToHex(const string& src) { string dest; size_t i; unsigned char symbol[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', }; dest.resize(src.size() * 2); char* append_ptr = &dest[0]; for (i = 0; i < src.size(); i++) { *append_ptr++ = symbol[(src[i] & 0xf0) >> 4]; *append_ptr++ = symbol[src[i] & 0x0f]; } return dest; } void Indent(io::Printer* printer) { printer->Indent(); printer->Indent(); } void Outdent(io::Printer* printer) { printer->Outdent(); printer->Outdent(); } void GenerateField(const FieldDescriptor* field, io::Printer* printer, bool is_descriptor) { if (field->is_repeated()) { GenerateFieldDocComment(printer, field); printer->Print( "private $^name^;\n", "name", field->name()); } else if (field->containing_oneof()) { // Oneof fields are handled by GenerateOneofField. return; } else { GenerateFieldDocComment(printer, field); printer->Print( "private $^name^ = ^default^;\n", "name", field->name(), "default", DefaultForField(field)); } if (is_descriptor) { printer->Print( "private $has_^name^ = false;\n", "name", field->name()); } } void GenerateOneofField(const OneofDescriptor* oneof, io::Printer* printer) { // Oneof property needs to be protected in order to be accessed by parent // class in implementation. printer->Print( "protected $^name^;\n", "name", oneof->name()); } void GenerateFieldAccessor(const FieldDescriptor* field, bool is_descriptor, io::Printer* printer) { const OneofDescriptor* oneof = field->containing_oneof(); // Generate getter. if (oneof != NULL) { GenerateFieldDocComment(printer, field); printer->Print( "public function get^camel_name^()\n" "{\n" " return $this->readOneof(^number^);\n" "}\n\n", "camel_name", UnderscoresToCamelCase(field->name(), true), "number", IntToString(field->number())); } else { GenerateFieldDocComment(printer, field); printer->Print( "public function get^camel_name^()\n" "{\n" " return $this->^name^;\n" "}\n\n", "camel_name", UnderscoresToCamelCase(field->name(), true), "name", field->name()); } // Generate setter. GenerateFieldDocComment(printer, field); printer->Print( "public function set^camel_name^(^var^)\n" "{\n", "camel_name", UnderscoresToCamelCase(field->name(), true), "var", (field->is_repeated() || field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) ? "&$var": "$var"); Indent(printer); // Type check. if (field->is_map()) { const Descriptor* map_entry = field->message_type(); const FieldDescriptor* key = map_entry->FindFieldByName("key"); const FieldDescriptor* value = map_entry->FindFieldByName("value"); printer->Print( "$arr = GPBUtil::checkMapField($var, " "\\Google\\Protobuf\\Internal\\GPBType::^key_type^, " "\\Google\\Protobuf\\Internal\\GPBType::^value_type^", "key_type", ToUpper(key->type_name()), "value_type", ToUpper(value->type_name())); if (value->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { printer->Print( ", \\^class_name^);\n", "class_name", MessageName(value->message_type(), is_descriptor) + "::class"); } else if (value->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) { printer->Print( ", ^class_name^);\n", "class_name", EnumName(value->enum_type(), is_descriptor) + "::class"); } else { printer->Print(");\n"); } } else if (field->is_repeated()) { printer->Print( "$arr = GPBUtil::checkRepeatedField($var, " "\\Google\\Protobuf\\Internal\\GPBType::^type^", "type", ToUpper(field->type_name())); if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { printer->Print( ", \\^class_name^);\n", "class_name", MessageName(field->message_type(), is_descriptor) + "::class"); } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) { printer->Print( ", ^class_name^);\n", "class_name", EnumName(field->enum_type(), is_descriptor) + "::class"); } else { printer->Print(");\n"); } } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { printer->Print( "GPBUtil::checkMessage($var, \\^class_name^::class);\n", "class_name", MessageName(field->message_type(), is_descriptor)); } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) { printer->Print( "GPBUtil::checkEnum($var, \\^class_name^::class);\n", "class_name", EnumName(field->enum_type(), is_descriptor)); } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_STRING) { printer->Print( "GPBUtil::checkString($var, ^utf8^);\n", "utf8", field->type() == FieldDescriptor::TYPE_STRING ? "True": "False"); } else { printer->Print( "GPBUtil::check^type^($var);\n", "type", UnderscoresToCamelCase(field->cpp_type_name(), true)); } if (oneof != NULL) { printer->Print( "$this->writeOneof(^number^, $var);\n", "number", IntToString(field->number())); } else if (field->is_repeated()) { printer->Print( "$this->^name^ = $arr;\n", "name", field->name()); } else { printer->Print( "$this->^name^ = $var;\n", "name", field->name()); } // Set has bit for proto2 only. if (is_descriptor) { printer->Print( "$this->has_^field_name^ = true;\n", "field_name", field->name()); } Outdent(printer); printer->Print( "}\n\n"); // Generate has method for proto2 only. if (is_descriptor) { printer->Print( "public function has^camel_name^()\n" "{\n" " return $this->has_^field_name^;\n" "}\n\n", "camel_name", UnderscoresToCamelCase(field->name(), true), "field_name", field->name()); } } void GenerateEnumToPool(const EnumDescriptor* en, io::Printer* printer) { printer->Print( "$pool->addEnum('^name^', " "\\Google\\Protobuf\\Internal\\^class_name^::class)\n", "name", EnumFullName(en, true), "class_name", en->name()); Indent(printer); for (int i = 0; i < en->value_count(); i++) { const EnumValueDescriptor* value = en->value(i); printer->Print( "->value(\"^name^\", ^number^)\n", "name", value->name(), "number", IntToString(value->number())); } printer->Print("->finalizeToPool();\n\n"); Outdent(printer); } void GenerateMessageToPool(const string& name_prefix, const Descriptor* message, io::Printer* printer) { // Don't generate MapEntry messages -- we use the PHP extension's native // support for map fields instead. if (message->options().map_entry()) { return; } string class_name = name_prefix.empty()? message->name() : name_prefix + "_" + message->name(); printer->Print( "$pool->addMessage('^message^', " "\\Google\\Protobuf\\Internal\\^class_name^::class)\n", "message", MessageFullName(message, true), "class_name", class_name); Indent(printer); for (int i = 0; i < message->field_count(); i++) { const FieldDescriptor* field = message->field(i); if (field->is_map()) { const FieldDescriptor* key = field->message_type()->FindFieldByName("key"); const FieldDescriptor* val = field->message_type()->FindFieldByName("value"); printer->Print( "->map('^field^', \\Google\\Protobuf\\Internal\\GPBType::^key^, " "\\Google\\Protobuf\\Internal\\GPBType::^value^, ^number^^other^)\n", "field", field->name(), "key", ToUpper(key->type_name()), "value", ToUpper(val->type_name()), "number", SimpleItoa(field->number()), "other", EnumOrMessageSuffix(val, true)); } else if (!field->containing_oneof()) { printer->Print( "->^label^('^field^', " "\\Google\\Protobuf\\Internal\\GPBType::^type^, ^number^^other^)\n", "field", field->name(), "label", LabelForField(field), "type", ToUpper(field->type_name()), "number", SimpleItoa(field->number()), "other", EnumOrMessageSuffix(field, true)); } } // oneofs. for (int i = 0; i < message->oneof_decl_count(); i++) { const OneofDescriptor* oneof = message->oneof_decl(i); printer->Print("->oneof(^name^)\n", "name", oneof->name()); Indent(printer); for (int index = 0; index < oneof->field_count(); index++) { const FieldDescriptor* field = oneof->field(index); printer->Print( "->value('^field^', " "\\Google\\Protobuf\\Internal\\GPBType::^type^, ^number^^other^)\n", "field", field->name(), "type", ToUpper(field->type_name()), "number", SimpleItoa(field->number()), "other", EnumOrMessageSuffix(field, true)); } printer->Print("->finish()\n"); Outdent(printer); } printer->Print( "->finalizeToPool();\n"); Outdent(printer); printer->Print( "\n"); for (int i = 0; i < message->nested_type_count(); i++) { GenerateMessageToPool(class_name, message->nested_type(i), printer); } for (int i = 0; i < message->enum_type_count(); i++) { GenerateEnumToPool(message->enum_type(i), printer); } } void GenerateAddFileToPool(const FileDescriptor* file, bool is_descriptor, io::Printer* printer) { printer->Print( "public static $is_initialized = false;\n\n" "public static function initOnce() {\n"); Indent(printer); printer->Print( "$pool = \\Google\\Protobuf\\Internal\\" "DescriptorPool::getGeneratedPool();\n\n" "if (static::$is_initialized == true) {\n" " return;\n" "}\n"); if (is_descriptor) { for (int i = 0; i < file->message_type_count(); i++) { GenerateMessageToPool("", file->message_type(i), printer); } for (int i = 0; i < file->enum_type_count(); i++) { GenerateEnumToPool(file->enum_type(i), printer); } printer->Print( "$pool->finish();\n"); } else { for (int i = 0; i < file->dependency_count(); i++) { const std::string& name = file->dependency(i)->name(); std::string dependency_filename = GeneratedMetadataFileName(name, is_descriptor); printer->Print( "\\^name^::initOnce();\n", "name", FilenameToClassname(dependency_filename)); } // Add messages and enums to descriptor pool. FileDescriptorSet files; FileDescriptorProto* file_proto = files.add_file(); file->CopyTo(file_proto); string files_data; files.SerializeToString(&files_data); printer->Print("$pool->internalAddGeneratedFile(hex2bin(\n"); Indent(printer); // Only write 30 bytes per line. static const int kBytesPerLine = 30; for (int i = 0; i < files_data.size(); i += kBytesPerLine) { printer->Print( "\"^data^\"^dot^\n", "data", BinaryToHex(files_data.substr(i, kBytesPerLine)), "dot", i + kBytesPerLine < files_data.size() ? " ." : ""); } Outdent(printer); printer->Print( "));\n\n"); } printer->Print( "static::$is_initialized = true;\n"); Outdent(printer); printer->Print("}\n"); } void GenerateUseDeclaration(bool is_descriptor, io::Printer* printer) { if (!is_descriptor) { printer->Print( "use Google\\Protobuf\\Internal\\GPBType;\n" "use Google\\Protobuf\\Internal\\RepeatedField;\n" "use Google\\Protobuf\\Internal\\GPBUtil;\n\n"); } else { printer->Print( "use Google\\Protobuf\\Internal\\GPBType;\n" "use Google\\Protobuf\\Internal\\GPBWire;\n" "use Google\\Protobuf\\Internal\\RepeatedField;\n" "use Google\\Protobuf\\Internal\\InputStream;\n\n" "use Google\\Protobuf\\Internal\\GPBUtil;\n\n"); } } void GenerateHead(const FileDescriptor* file, io::Printer* printer) { printer->Print( "name()); } std::string FilenameToClassname(const string& filename) { int lastindex = filename.find_last_of("."); std::string result = filename.substr(0, lastindex); for (int i = 0; i < result.size(); i++) { if (result[i] == '/') { result[i] = '\\'; } } return result; } void GenerateMetadataFile(const FileDescriptor* file, bool is_descriptor, GeneratorContext* generator_context) { std::string filename = GeneratedMetadataFileName(file->name(), is_descriptor); scoped_ptr output( generator_context->Open(filename)); io::Printer printer(output.get(), '^'); GenerateHead(file, &printer); std::string fullname = FilenameToClassname(filename); int lastindex = fullname.find_last_of("\\"); printer.Print( "namespace ^name^;\n\n", "name", fullname.substr(0, lastindex)); if (lastindex != string::npos) { printer.Print( "class ^name^\n" "{\n", "name", fullname.substr(lastindex + 1)); } else { printer.Print( "class ^name^\n" "{\n", "name", fullname); } Indent(&printer); GenerateAddFileToPool(file, is_descriptor, &printer); Outdent(&printer); printer.Print("}\n\n"); } void GenerateEnumFile(const FileDescriptor* file, const EnumDescriptor* en, bool is_descriptor, GeneratorContext* generator_context) { std::string filename = GeneratedEnumFileName(en, is_descriptor); scoped_ptr output( generator_context->Open(filename)); io::Printer printer(output.get(), '^'); GenerateHead(file, &printer); std::string fullname = FilenameToClassname(filename); int lastindex = fullname.find_last_of("\\"); if (!file->package().empty()) { printer.Print( "namespace ^name^;\n\n", "name", fullname.substr(0, lastindex)); } GenerateEnumDocComment(&printer, en); if (lastindex != string::npos) { printer.Print( "class ^name^\n" "{\n", "name", fullname.substr(lastindex + 1)); } else { printer.Print( "class ^name^\n" "{\n", "name", fullname); } Indent(&printer); for (int i = 0; i < en->value_count(); i++) { const EnumValueDescriptor* value = en->value(i); GenerateEnumValueDocComment(&printer, value); printer.Print("const ^name^ = ^number^;\n", "name", value->name(), "number", IntToString(value->number())); } Outdent(&printer); printer.Print("}\n\n"); } void GenerateMessageFile(const FileDescriptor* file, const Descriptor* message, bool is_descriptor, GeneratorContext* generator_context) { // Don't generate MapEntry messages -- we use the PHP extension's native // support for map fields instead. if (message->options().map_entry()) { return; } std::string filename = GeneratedMessageFileName(message, is_descriptor); scoped_ptr output( generator_context->Open(filename)); io::Printer printer(output.get(), '^'); GenerateHead(file, &printer); std::string fullname = FilenameToClassname(filename); int lastindex = fullname.find_last_of("\\"); if (!file->package().empty()) { printer.Print( "namespace ^name^;\n\n", "name", fullname.substr(0, lastindex)); } GenerateUseDeclaration(is_descriptor, &printer); GenerateMessageDocComment(&printer, message); if (lastindex != string::npos) { printer.Print( "class ^name^ extends \\Google\\Protobuf\\Internal\\Message\n" "{\n", "name", fullname.substr(lastindex + 1)); } else { printer.Print( "class ^name^ extends \\Google\\Protobuf\\Internal\\Message\n" "{\n", "name", fullname); } Indent(&printer); // Field and oneof definitions. for (int i = 0; i < message->field_count(); i++) { const FieldDescriptor* field = message->field(i); GenerateField(field, &printer, is_descriptor); } for (int i = 0; i < message->oneof_decl_count(); i++) { const OneofDescriptor* oneof = message->oneof_decl(i); GenerateOneofField(oneof, &printer); } printer.Print("\n"); printer.Print( "public function __construct() {\n"); Indent(&printer); std::string metadata_filename = GeneratedMetadataFileName(file->name(), is_descriptor); std::string metadata_fullname = FilenameToClassname(metadata_filename); printer.Print( "\\^fullname^::initOnce();\n" "parent::__construct();\n", "fullname", metadata_fullname); Outdent(&printer); printer.Print("}\n\n"); // Field and oneof accessors. for (int i = 0; i < message->field_count(); i++) { const FieldDescriptor* field = message->field(i); GenerateFieldAccessor(field, is_descriptor, &printer); } for (int i = 0; i < message->oneof_decl_count(); i++) { const OneofDescriptor* oneof = message->oneof_decl(i); printer.Print( "public function get^camel_name^()\n" "{\n" " return $this->whichOneof(\"^name^\");\n" "}\n\n", "camel_name", UnderscoresToCamelCase(oneof->name(), true), "name", oneof->name()); } Outdent(&printer); printer.Print("}\n\n"); // Nested messages and enums. for (int i = 0; i < message->nested_type_count(); i++) { GenerateMessageFile(file, message->nested_type(i), is_descriptor, generator_context); } for (int i = 0; i < message->enum_type_count(); i++) { GenerateEnumFile(file, message->enum_type(i), is_descriptor, generator_context); } } void GenerateFile(const FileDescriptor* file, bool is_descriptor, GeneratorContext* generator_context) { GenerateMetadataFile(file, is_descriptor, generator_context); for (int i = 0; i < file->message_type_count(); i++) { GenerateMessageFile(file, file->message_type(i), is_descriptor, generator_context); } for (int i = 0; i < file->enum_type_count(); i++) { GenerateEnumFile(file, file->enum_type(i), is_descriptor, generator_context); } } static string EscapePhpdoc(const string& input) { string result; result.reserve(input.size() * 2); char prev = '*'; for (string::size_type i = 0; i < input.size(); i++) { char c = input[i]; switch (c) { case '*': // Avoid "/*". if (prev == '/') { result.append("*"); } else { result.push_back(c); } break; case '/': // Avoid "*/". if (prev == '*') { result.append("/"); } else { result.push_back(c); } break; case '@': // '@' starts phpdoc tags including the @deprecated tag, which will // cause a compile-time error if inserted before a declaration that // does not have a corresponding @Deprecated annotation. result.append("@"); break; case '<': // Avoid interpretation as HTML. result.append("<"); break; case '>': // Avoid interpretation as HTML. result.append(">"); break; case '&': // Avoid interpretation as HTML. result.append("&"); break; case '\\': // Java interprets Unicode escape sequences anywhere! result.append("\"); break; default: result.push_back(c); break; } prev = c; } return result; } static void GenerateDocCommentBodyForLocation( io::Printer* printer, const SourceLocation& location) { string comments = location.leading_comments.empty() ? location.trailing_comments : location.leading_comments; if (!comments.empty()) { // TODO(teboring): Ideally we should parse the comment text as Markdown and // write it back as HTML, but this requires a Markdown parser. For now // we just use
 to get fixed-width text formatting.

    // If the comment itself contains block comment start or end markers,
    // HTML-escape them so that they don't accidentally close the doc comment.
    comments = EscapePhpdoc(comments);

    vector lines = Split(comments, "\n");
    while (!lines.empty() && lines.back().empty()) {
      lines.pop_back();
    }

    printer->Print(" * 
\n");
    for (int i = 0; i < lines.size(); i++) {
      // Most lines should start with a space.  Watch out for lines that start
      // with a /, since putting that right after the leading asterisk will
      // close the comment.
      if (!lines[i].empty() && lines[i][0] == '/') {
        printer->Print(" * ^line^\n", "line", lines[i]);
      } else {
        printer->Print(" *^line^\n", "line", lines[i]);
      }
    }
    printer->Print(
        " * 
\n" " *\n"); } } template static void GenerateDocCommentBody( io::Printer* printer, const DescriptorType* descriptor) { SourceLocation location; if (descriptor->GetSourceLocation(&location)) { GenerateDocCommentBodyForLocation(printer, location); } } static string FirstLineOf(const string& value) { string result = value; string::size_type pos = result.find_first_of('\n'); if (pos != string::npos) { result.erase(pos); } return result; } void GenerateMessageDocComment(io::Printer* printer, const Descriptor* message) { printer->Print("/**\n"); GenerateDocCommentBody(printer, message); printer->Print( " * Protobuf type ^fullname^\n" " */\n", "fullname", EscapePhpdoc(message->full_name())); } void GenerateFieldDocComment(io::Printer* printer, const FieldDescriptor* field) { // In theory we should have slightly different comments for setters, getters, // etc., but in practice everyone already knows the difference between these // so it's redundant information. // We start the comment with the main body based on the comments from the // .proto file (if present). We then end with the field declaration, e.g.: // optional string foo = 5; // If the field is a group, the debug string might end with {. printer->Print("/**\n"); GenerateDocCommentBody(printer, field); printer->Print( " * ^def^\n", "def", EscapePhpdoc(FirstLineOf(field->DebugString()))); printer->Print(" */\n"); } void GenerateEnumDocComment(io::Printer* printer, const EnumDescriptor* enum_) { printer->Print("/**\n"); GenerateDocCommentBody(printer, enum_); printer->Print( " * Protobuf enum ^fullname^\n" " */\n", "fullname", EscapePhpdoc(enum_->full_name())); } void GenerateEnumValueDocComment(io::Printer* printer, const EnumValueDescriptor* value) { printer->Print("/**\n"); GenerateDocCommentBody(printer, value); printer->Print( " * ^def^\n" " */\n", "def", EscapePhpdoc(FirstLineOf(value->DebugString()))); } bool Generator::Generate(const FileDescriptor* file, const string& parameter, GeneratorContext* generator_context, string* error) const { bool is_descriptor = parameter == "internal"; if (is_descriptor && file->name() != kDescriptorFile) { *error = "Can only generate PHP code for google/protobuf/descriptor.proto.\n"; return false; } if (!is_descriptor && file->syntax() != FileDescriptor::SYNTAX_PROTO3) { *error = "Can only generate PHP code for proto3 .proto files.\n" "Please add 'syntax = \"proto3\";' to the top of your .proto file.\n"; return false; } GenerateFile(file, is_descriptor, generator_context); return true; } } // namespace php } // namespace compiler } // namespace protobuf } // namespace google