Add __autoload() support to Thrift-generated PHP code

Summary: Include thrift/autoload.php and use -phpa flag to generated code that works with autoload. Good for services with lots of methods that are typically not all invoked.

Reviewed By: dreiss

Test Plan: Falcon, baby, falcon.


git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@665349 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/compiler/cpp/src/generate/t_php_generator.cc b/compiler/cpp/src/generate/t_php_generator.cc
index c0fdd25..8a3bdb7 100644
--- a/compiler/cpp/src/generate/t_php_generator.cc
+++ b/compiler/cpp/src/generate/t_php_generator.cc
@@ -359,6 +359,28 @@
 }
 
 
+void t_php_generator::generate_php_struct_definition(ofstream& out,
+                                                     t_struct* tstruct,
+                                                     bool is_exception) {
+  if (autoload_) {
+    // Make output file
+    ofstream autoload_out;
+    string f_struct = program_name_+"."+(tstruct->get_name())+".php";
+    string f_struct_name = get_out_dir()+f_struct;
+    autoload_out.open(f_struct_name.c_str());
+    autoload_out << "<?php" << endl;
+    _generate_php_struct_definition(autoload_out, tstruct, is_exception);
+    autoload_out << endl << "?>" << endl;
+    autoload_out.close();
+
+    f_types_ <<
+      "$GLOBALS['THRIFT_AUTOLOAD']['" << php_namespace(tstruct->get_program()) << tstruct->get_name() << "'] = '" << program_name_ << "/" << f_struct << "';" << endl;
+
+  } else {
+    _generate_php_struct_definition(out, tstruct, is_exception);
+  }
+}
+
 /**
  * Generates a struct definition for a thrift data type. This is nothing in PHP
  * where the objects are all just associative arrays (unless of course we
@@ -366,7 +388,7 @@
  *
  * @param tstruct The struct definition
  */
-void t_php_generator::generate_php_struct_definition(ofstream& out,
+void t_php_generator::_generate_php_struct_definition(ofstream& out,
                                                      t_struct* tstruct,
                                                      bool is_exception) {
   const vector<t_field*>& members = tstruct->get_members();
@@ -1032,12 +1054,32 @@
     "}" << endl << endl;
 }
 
+void t_php_generator::generate_service_client(t_service* tservice) {
+  if (autoload_) {
+    // Make output file
+    ofstream autoload_out;
+    string f_struct = program_name_+"."+(tservice->get_name())+".client.php";
+    string f_struct_name = get_out_dir()+f_struct;
+    autoload_out.open(f_struct_name.c_str());
+    autoload_out << "<?php" << endl;
+    _generate_service_client(autoload_out, tservice);
+    autoload_out << endl << "?>" << endl;
+    autoload_out.close();
+
+    f_service_ <<
+      "$GLOBALS['THRIFT_AUTOLOAD']['" << service_name_ << "Client" << "'] = '" << program_name_ << "/" << f_struct << "';" << endl;
+
+  } else {
+    _generate_service_client(f_service_, tservice);
+  }
+}
+
 /**
  * Generates a service client definition.
  *
  * @param tservice The service to generate a server for.
  */
-void t_php_generator::generate_service_client(t_service* tservice) {
+void t_php_generator::_generate_service_client(ofstream& out, t_service* tservice) {
   string extends = "";
   string extends_client = "";
   if (tservice->get_extends() != NULL) {
@@ -1045,33 +1087,33 @@
     extends_client = " extends " + extends + "Client";
   }
 
-  f_service_ <<
+  out <<
     "class " << service_name_ << "Client" << extends_client << " implements " << service_name_ << "If {" << endl;
   indent_up();
 
   // Private members
   if (extends.empty()) {
-    f_service_ <<
+    out <<
       indent() << "protected $input_ = null;" << endl <<
       indent() << "protected $output_ = null;" << endl <<
       endl;
-    f_service_ <<
+    out <<
       indent() << "protected $seqid_ = 0;" << endl <<
       endl;
   }
 
   // Constructor function
-  f_service_ <<
+  out <<
     indent() << "public function __construct($input, $output=null) {" << endl;
   if (!extends.empty()) {
-    f_service_ <<
+    out <<
       indent() << "  parent::__construct($input, $output);" << endl;
   } else {
-    f_service_ <<
+    out <<
       indent() << "  $this->input_ = $input;" << endl <<
       indent() << "  $this->output_ = $output ? $output : $input;" << endl;
   }
-  f_service_ <<
+  out <<
     indent() << "}" << endl << endl;
 
   // Generate client method implementations
@@ -1084,10 +1126,10 @@
     string funname = (*f_iter)->get_name();
 
     // Open function
-    indent(f_service_) <<
+    indent(out) <<
       "public function " << function_signature(*f_iter) << endl;
-    scope_up(f_service_);
-      indent(f_service_) <<
+    scope_up(out);
+      indent(out) <<
         "$this->send_" << funname << "(";
 
       bool first = true;
@@ -1095,63 +1137,63 @@
         if (first) {
           first = false;
         } else {
-          f_service_ << ", ";
+          out << ", ";
         }
-        f_service_ << "$" << (*fld_iter)->get_name();
+        out << "$" << (*fld_iter)->get_name();
       }
-      f_service_ << ");" << endl;
+      out << ");" << endl;
 
       if (!(*f_iter)->is_async()) {
-        f_service_ << indent();
+        out << indent();
         if (!(*f_iter)->get_returntype()->is_void()) {
-          f_service_ << "return ";
+          out << "return ";
         }
-        f_service_ <<
+        out <<
           "$this->recv_" << funname << "();" << endl;
       }
-    scope_down(f_service_);
-    f_service_ << endl;
+    scope_down(out);
+    out << endl;
 
-    indent(f_service_) <<
+    indent(out) <<
       "public function send_" << function_signature(*f_iter) << endl;
-    scope_up(f_service_);
+    scope_up(out);
 
       std::string argsname = php_namespace(tservice->get_program()) + service_name_ + "_" + (*f_iter)->get_name() + "_args";
 
       // Serialize the request header
       if (binary_inline_) {
-        f_service_ <<
+        out <<
           indent() << "$buff = pack('N', (0x80010000 | TMessageType::CALL));" << endl <<
           indent() << "$buff .= pack('N', strlen('" << funname << "'));" << endl <<
           indent() << "$buff .= '" << funname << "';" << endl <<
           indent() << "$buff .= pack('N', $this->seqid_);" << endl;
       } else {
-        f_service_ <<
+        out <<
           indent() << "$this->output_->writeMessageBegin('" << (*f_iter)->get_name() << "', TMessageType::CALL, $this->seqid_);" << endl;
       }
 
-      f_service_ <<
+      out <<
         indent() << "$args = new " << argsname << "();" << endl;
 
       for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) {
-        f_service_ <<
+        out <<
           indent() << "$args->" << (*fld_iter)->get_name() << " = $" << (*fld_iter)->get_name() << ";" << endl;
       }
 
       // Write to the stream
       if (binary_inline_) {
-        f_service_ <<
+        out <<
           indent() << "$args->write($buff);" << endl <<
           indent() << "$this->output_->write($buff);" << endl <<
           indent() << "$this->output_->flush();" << endl;
       } else {
-        f_service_ <<
+        out <<
           indent() << "$args->write($this->output_);" << endl <<
           indent() << "$this->output_->writeMessageEnd();" << endl <<
           indent() << "$this->output_->getTransport()->flush();" << endl;
       }
 
-    scope_down(f_service_);
+    scope_down(out);
 
 
     if (!(*f_iter)->is_async()) {
@@ -1162,12 +1204,12 @@
                                string("recv_") + (*f_iter)->get_name(),
                                &noargs);
       // Open function
-      f_service_ <<
+      out <<
         endl <<
         indent() << "public function " << function_signature(&recv_function) << endl;
-      scope_up(f_service_);
+      scope_up(out);
 
-      f_service_ <<
+      out <<
         indent() << "$rseqid = 0;" << endl <<
         indent() << "$fname = null;" << endl <<
         indent() << "$mtype = 0;" << endl <<
@@ -1176,16 +1218,16 @@
       if (binary_inline_) {
         t_field ffname(g_type_string, "fname");
         t_field fseqid(g_type_i32, "rseqid");
-        f_service_ <<
+        out <<
           indent() << "$ver = unpack('N', $this->input_->readAll(4));" << endl <<
           indent() << "$ver = $ver[1];" << endl <<
           indent() << "$mtype = $ver & 0xff;" << endl <<
           indent() << "$ver = $ver & 0xffff0000;" << endl <<
           indent() << "if ($ver != 0x80010000) throw new TProtocolException('Bad version identifier: '.$ver, TProtocolException::BAD_VERSION);" << endl;
-        generate_deserialize_field(f_service_, &ffname, "", true);
-        generate_deserialize_field(f_service_, &fseqid, "", true);
+        generate_deserialize_field(out, &ffname, "", true);
+        generate_deserialize_field(out, &fseqid, "", true);
       } else {
-        f_service_ <<
+        out <<
           indent() << "$this->input_->readMessageBegin($fname, $mtype, $rseqid);" << endl <<
           indent() << "if ($mtype == TMessageType::EXCEPTION) {" << endl <<
           indent() << "  $x = new TApplicationException();" << endl <<
@@ -1195,19 +1237,19 @@
           indent() << "}" << endl;
       }
 
-      f_service_ <<
+      out <<
         indent() << "$result = new " << resultname << "();" << endl <<
         indent() << "$result->read($this->input_);" << endl;
 
       if (!binary_inline_) {
-        f_service_ <<
+        out <<
           indent() << "$this->input_->readMessageEnd();" << endl <<
           endl;
       }
 
       // Careful, only return result if not a void function
       if (!(*f_iter)->get_returntype()->is_void()) {
-        f_service_ <<
+        out <<
           indent() << "if ($result->success !== null) {" << endl <<
           indent() << "  return $result->success;" << endl <<
           indent() << "}" << endl;
@@ -1217,7 +1259,7 @@
       const std::vector<t_field*>& xceptions = xs->get_members();
       vector<t_field*>::const_iterator x_iter;
       for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) {
-        f_service_ <<
+        out <<
           indent() << "if ($result->" << (*x_iter)->get_name() << " !== null) {" << endl <<
           indent() << "  throw $result->" << (*x_iter)->get_name() << ";" << endl <<
           indent() << "}" << endl;
@@ -1225,22 +1267,22 @@
 
       // Careful, only return _result if not a void function
       if ((*f_iter)->get_returntype()->is_void()) {
-        indent(f_service_) <<
+        indent(out) <<
           "return;" << endl;
       } else {
-        f_service_ <<
+        out <<
           indent() << "throw new Exception(\"" << (*f_iter)->get_name() << " failed: unknown result\");" << endl;
       }
 
     // Close function
-    scope_down(f_service_);
-    f_service_ << endl;
+    scope_down(out);
+    out << endl;
 
     }
   }
 
   indent_down();
-  f_service_ <<
+  out <<
     "}" << endl << endl;
 }
 
diff --git a/compiler/cpp/src/generate/t_php_generator.h b/compiler/cpp/src/generate/t_php_generator.h
index 5742747..3f85fea 100644
--- a/compiler/cpp/src/generate/t_php_generator.h
+++ b/compiler/cpp/src/generate/t_php_generator.h
@@ -21,11 +21,16 @@
  */
 class t_php_generator : public t_oop_generator {
  public:
-  t_php_generator(t_program* program, bool binary_inline=false, bool rest=false, bool phps=false) :
+  t_php_generator(t_program* program,
+                  bool binary_inline=false,
+                  bool rest=false,
+                  bool phps=false,
+                  bool autoload=false) :
     t_oop_generator(program),
     binary_inline_(binary_inline),
     rest_(rest),
-    phps_(phps) {
+    phps_(phps),
+    autoload_(autoload) {
     out_dir_base_ = (binary_inline_ ? "gen-phpi" : "gen-php");
   }
 
@@ -55,6 +60,7 @@
 
   void generate_php_struct(t_struct* tstruct, bool is_exception);
   void generate_php_struct_definition(std::ofstream& out, t_struct* tstruct, bool is_xception=false);
+  void _generate_php_struct_definition(std::ofstream& out, t_struct* tstruct, bool is_xception=false);
   void generate_php_struct_reader(std::ofstream& out, t_struct* tstruct);
   void generate_php_struct_writer(std::ofstream& out, t_struct* tstruct);
   void generate_php_function_helpers(t_function* tfunction);
@@ -70,6 +76,7 @@
   void generate_service_interface (t_service* tservice);
   void generate_service_rest      (t_service* tservice);
   void generate_service_client    (t_service* tservice);
+  void _generate_service_client   (std::ofstream &out, t_service* tservice);
   void generate_service_processor (t_service* tservice);
   void generate_process_function  (t_service* tservice, t_function* tfunction);
 
@@ -168,6 +175,11 @@
    */
   bool phps_;
 
+  /**
+   * Generate PHP code that uses autoload
+   */
+  bool autoload_;
+
 };
 
 #endif
diff --git a/compiler/cpp/src/main.cc b/compiler/cpp/src/main.cc
index c633fac..0b904ea 100644
--- a/compiler/cpp/src/main.cc
+++ b/compiler/cpp/src/main.cc
@@ -140,6 +140,7 @@
 bool gen_php = false;
 bool gen_phpi = false;
 bool gen_phps = true;
+bool gen_phpa = false;
 bool gen_rest = false;
 bool gen_perl = false;
 bool gen_erl = false;
@@ -562,6 +563,7 @@
   fprintf(stderr, "  -phpi       Generate PHP inlined files\n");
   fprintf(stderr, "  -phps       Generate PHP server stubs (with -php)\n");
   fprintf(stderr, "  -phpl       Generate PHP-lite (with -php)\n");
+  fprintf(stderr, "  -phpa       Generate PHP with autoload (with -php)\n");
   fprintf(stderr, "  -py         Generate Python output files\n");
   fprintf(stderr, "  -rb         Generate Ruby output files\n");
   fprintf(stderr, "  -xsd        Generate XSD output files\n");
@@ -813,7 +815,7 @@
 
     if (gen_php) {
       pverbose("Generating PHP\n");
-      t_php_generator* php = new t_php_generator(program, false, gen_rest, gen_phps);
+      t_php_generator* php = new t_php_generator(program, false, gen_rest, gen_phps, gen_phpa);
       php->generate_program();
       delete php;
     }
@@ -953,6 +955,12 @@
           gen_php = true;
         }
         gen_phps = false;
+      } else if (strcmp(arg, "-phpa") == 0) {
+        if (!gen_php) {
+          gen_php = true;
+          gen_phps = false;
+        }
+        gen_phpa = true;
       } else if (strcmp(arg, "-rest") == 0) {
         gen_rest = true;
       } else if (strcmp(arg, "-py") == 0) {
diff --git a/lib/php/src/autoload.php b/lib/php/src/autoload.php
new file mode 100644
index 0000000..b5886c5
--- /dev/null
+++ b/lib/php/src/autoload.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * Copyright (c) 2006- Facebook
+ * Distributed under the Thrift Software License
+ *
+ * See accompanying file LICENSE or visit the Thrift site at:
+ * http://developers.facebook.com/thrift/
+ *
+ * @package thrift
+ * @author Mark Slee <mcslee@facebook.com>
+ */
+
+/**
+ * Include this file if you wish to use autoload with your PHP generated Thrift
+ * code. The generated code will *not* include any defined Thrift classes by
+ * default, except for the service interfaces. The generated code will populate
+ * values into $GLOBALS['THRIFT_AUTOLOAD'] which can be used by the autoload
+ * method below. If you have your own autoload system already in place, you
+ * should merge the following functionality into your autoload system.
+ *
+ * Generate this code using the -phpa Thrift generator flag.
+ */
+
+$GLOBALS['THRIFT_AUTOLOAD'] = array();
+
+if (!function_exists('__autoload')) {
+  function __autoload($class) {
+    global $THRIFT_AUTOLOAD;
+    if (isset($THRIFT_AUTOLOAD[$class])) {
+      include_once $GLOBALS['THRIFT_ROOT'].'/lib/packages/'.$THRIFT_AUTOLOAD[$class];
+    }
+  }
+}