THRIFT-2330 Generate PHPDoc comments
Patch: Maurus Cuelenaere
diff --git a/compiler/cpp/src/generate/t_php_generator.cc b/compiler/cpp/src/generate/t_php_generator.cc
index 833caef..0f48758 100644
--- a/compiler/cpp/src/generate/t_php_generator.cc
+++ b/compiler/cpp/src/generate/t_php_generator.cc
@@ -186,6 +186,18 @@
                                           t_list*     tlist,
                                           std::string iter);
 
+  void generate_php_doc                  (std::ofstream& out,
+                                          t_doc*      tdoc);
+
+  void generate_php_doc                  (std::ofstream& out,
+                                          t_field*    tfield);
+
+  void generate_php_doc                  (std::ofstream& out,
+                                          t_function* tfunction);
+
+  void generate_php_docstring_comment    (std::ofstream &out,
+                                          string contents);
+
   /**
    * Helper rendering functions
    */
@@ -196,6 +208,7 @@
   std::string argument_list(t_struct* tstruct, bool addStructSignature = true);
   std::string type_to_cast(t_type* ttype);
   std::string type_to_enum(t_type* ttype);
+  std::string type_to_phpdoc(t_type* ttype);
 
   std::string php_namespace_base(const t_program* p) {
     std::string ns = p->get_namespace("php");
@@ -440,12 +453,14 @@
   // We're also doing it this way to see how it performs. It's more legible
   // code but you can't do things like an 'extract' on it, which is a bit of
   // a downer.
+  generate_php_doc(f_types_, tenum);
   f_types_ <<
     "final class " << tenum->get_name() << " {" << endl;
   indent_up();
 
   for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) {
     int value = (*c_iter)->get_value();
+    generate_php_doc(f_types_, *c_iter);
     indent(f_types_) <<
       "const " << (*c_iter)->get_name() << " = " << value << ";" << endl;
   }
@@ -472,6 +487,7 @@
   string name = tconst->get_name();
   t_const_value* value = tconst->get_value();
 
+  generate_php_doc(f_types_, tconst);
   f_types_ << "$GLOBALS['" << program_name_ << "_CONSTANTS']['" << name << "'] = ";
   f_types_ << render_const_value(type, value);
   f_types_ << ";" << endl << endl;
@@ -690,6 +706,7 @@
   const vector<t_field*>& members = tstruct->get_members();
   vector<t_field*>::const_iterator m_iter;
 
+  generate_php_doc(out, tstruct);
   out <<
     "class " << php_namespace_declaration(tstruct);
   if (is_exception) {
@@ -709,6 +726,7 @@
     if ((*m_iter)->get_value() != NULL && !(t->is_struct() || t->is_xception())) {
       dval = render_const_value((*m_iter)->get_type(), (*m_iter)->get_value());
     }
+    generate_php_doc(out, *m_iter);
     indent(out) <<
       "public $" << (*m_iter)->get_name() << " = " << dval << ";" << endl;
   }
@@ -1304,12 +1322,14 @@
     extends = " extends " + php_namespace(tservice->get_extends()->get_program()) + tservice->get_extends()->get_name();
     extends_if = " extends " + php_namespace(tservice->get_extends()->get_program()) + tservice->get_extends()->get_name() + "If";
   }
+  generate_php_doc(f_service_, tservice);
   f_service_ <<
     "interface " << php_namespace_declaration(tservice) << "If" << extends_if << " {" << endl;
   indent_up();
   vector<t_function*> functions = tservice->get_functions();
   vector<t_function*>::iterator f_iter;
   for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
+    generate_php_doc(f_service_, *f_iter);
     indent(f_service_) <<
       "public function " << function_signature(*f_iter) << ";" << endl;
   }
@@ -2205,6 +2225,93 @@
 }
 
 /**
+ * Emits a PHPDoc comment for the given contents
+ */
+void t_php_generator::generate_php_docstring_comment(ofstream &out, string contents) {
+  generate_docstring_comment(out,
+                             "/**\n",
+                             " * ", contents,
+                             " */\n");
+}
+
+/**
+ * Emits a PHPDoc comment if the provided object has a doc in Thrift
+ */
+void t_php_generator::generate_php_doc(ofstream &out, t_doc* tdoc) {
+  if (tdoc->has_doc()) {
+    generate_php_docstring_comment(out, tdoc->get_doc());
+  }
+}
+
+/**
+ * Emits a PHPDoc comment for a field
+ */
+void t_php_generator::generate_php_doc(ofstream &out, t_field* field) {
+  stringstream ss;
+
+  // prepend free-style doc if available
+  if (field->has_doc()) {
+    ss << field->get_doc() << endl;
+  }
+
+  // append @var tag
+  t_type* type = get_true_type(field->get_type());
+  ss << "@var " << type_to_phpdoc(type) << endl;
+
+  generate_php_docstring_comment(out, ss.str());
+}
+
+/**
+ * Emits a PHPDoc comment for a function
+ */
+void t_php_generator::generate_php_doc(ofstream &out, t_function* function) {
+  stringstream ss;
+  if (function->has_doc()) {
+    ss << function->get_doc() << endl;
+  }
+
+  // generate parameter types doc
+  const vector<t_field*>& args = function->get_arglist()->get_members();
+  vector<t_field*>::const_iterator a_iter;
+  for (a_iter = args.begin(); a_iter != args.end(); ++a_iter) {
+    t_field* arg = *a_iter;
+    ss << "@param " << type_to_phpdoc(arg->get_type()) << " $" << arg->get_name();
+    if (arg->has_doc()) {
+      ss << " " << arg->get_doc();
+    }
+    ss << endl;
+  }
+
+  // generate return type doc
+  t_type* ret_type = function->get_returntype();
+  if (!ret_type->is_void() || ret_type->has_doc()) {
+    ss << "@return " << type_to_phpdoc(ret_type);
+    if (ret_type->has_doc()) {
+      ss << " " << ret_type->get_doc();
+    }
+    ss << endl;
+  }
+
+  // generate exceptions doc
+  const vector<t_field*>& excs = function->get_xceptions()->get_members();
+  vector<t_field*>::const_iterator e_iter;
+  for (e_iter = excs.begin(); e_iter != excs.end(); ++e_iter) {
+    t_field* exc = *e_iter;
+    ss << "@throws " << type_to_phpdoc(exc->get_type());
+    if (exc->has_doc()) {
+      ss << " " << exc->get_doc();
+    }
+    ss << endl;
+  }
+
+  generate_docstring_comment(out,
+                           "/**\n",
+                           " * ", ss.str(),
+                           " */\n");
+}
+
+
+/**
  * Declares a field, which may include initialization as necessary.
  *
  * @param ttype The type
@@ -2363,6 +2470,59 @@
   throw "INVALID TYPE IN type_to_enum: " + type->get_name();
 }
 
+/**
+ * Converts the parse type to a PHPDoc string for the given type.
+ */
+string t_php_generator ::type_to_phpdoc(t_type* type) {
+  type = get_true_type(type);
+
+  if (type->is_base_type()) {
+    t_base_type::t_base tbase = ((t_base_type*)type)->get_base();
+    switch (tbase) {
+    case t_base_type::TYPE_VOID:
+      return "void";
+    case t_base_type::TYPE_STRING:
+      return "string";
+    case t_base_type::TYPE_BOOL:
+      return "bool";
+    case t_base_type::TYPE_BYTE:
+      return "int";
+    case t_base_type::TYPE_I16:
+      return "int";
+    case t_base_type::TYPE_I32:
+      return "int";
+    case t_base_type::TYPE_I64:
+      return "int";
+    case t_base_type::TYPE_DOUBLE:
+      return "double";
+    }
+  } else if (type->is_enum()) {
+    return "int";
+  } else if (type->is_struct() || type->is_xception()) {
+    return php_namespace(type->get_program()) + type->get_name();
+  } else if (type->is_map()) {
+    return "array";
+  } else if (type->is_set()) {
+    t_set* tset = static_cast<t_set*>(type);
+    t_type* t_elem = tset->get_elem_type();
+    if (t_elem->is_container()) {
+      return "(" + type_to_phpdoc(t_elem) + ")[]";
+    } else {
+      return type_to_phpdoc(t_elem) + "[]";
+    }
+  } else if (type->is_list()) {
+    t_list* tlist = static_cast<t_list*>(type);
+    t_type* t_elem = tlist->get_elem_type();
+    if (t_elem->is_container()) {
+      return "(" + type_to_phpdoc(t_elem) + ")[]";
+    } else {
+      return type_to_phpdoc(t_elem) + "[]";
+    }
+  }
+
+  throw "INVALID TYPE IN type_to_enum: " + type->get_name();
+}
+
 THRIFT_REGISTER_GENERATOR(php, "PHP",
 "    inlined:         Generate PHP inlined files\n"
 "    server:          Generate PHP server stubs\n"