Thrift php generator in python.
Cleaned up parser and cpp generator
git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@664765 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/compiler/src/php_generator.py b/compiler/src/php_generator.py
new file mode 100644
index 0000000..e68a4f7
--- /dev/null
+++ b/compiler/src/php_generator.py
@@ -0,0 +1,1015 @@
+""" Thrift IDL PHP client stub generator
+
+ This generated PHP class definitions and client stubs for a valid thrift IDL definition
+
+ Author(s): Mark Slee(mclee@facebook.com), Marc Kwiatkowski (marc@facebook.com)
+
+ $Id:
+"""
+import time
+
+import os
+import os.path
+from string import Template
+from thrift.parser import *
+from thrift.generator import *
+
+HEADER_COMMENT = """<?php
+/**
+ * Autogenerated by Thrift
+ * ${date}
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+ """
+
+PHP_TYPES_HEADER = Template(HEADER_COMMENT+"""
+require_once THRIFT_ROOT.\'/Thrift.php\';
+""")
+
+PHP_TYPES_FOOTER = Template("""
+?>
+""")
+
+PHP_SERVICES_HEADER = Template(HEADER_COMMENT+"""
+
+require_once THRIFT_ROOT.'/Thrift.php';
+require_once dirname(__FILE__).\'/${source}_types.php\';
+
+""")
+
+PHP_SERVICES_FOOTER = Template("""
+?>
+""")
+
+PHP_IMPL_HEADER = Template(HEADER_COMMENT+"""
+
+require_once THRIFT_ROOT.'/Thrift.php';
+require_once dirname(__FILE__).\'/${source}_types.php\';
+
+""")
+
+PHP_IMPL_FOOTER = Template("""
+?>
+""")
+
+def php_debug(arg):
+ print(arg)
+
+class Indenter(object):
+ def __init__(self, margin=4):
+ self.margin = ''.join([' ' for ix in range(margin)])
+ self.level = 0
+
+ def __getitem__(self, level):
+ return ''.join([self.margin for ix in range(level)])
+
+ def __iadd__(self, value):
+ self.level = self.level + value
+ return self
+
+ def __isub__(self, value):
+ self.level = self.level - value
+ return self
+
+ def __call__(self):
+ return self.__getitem__(self.level)
+
+class Variable(object):
+ def __init__(self, ttype, name):
+ self.ttype = ttype
+ self.name = name
+
+ def toDeclaration(self, new=False):
+ if not new:
+ defaultValue = defaultValueForType(self.ttype)
+ else:
+ defaultValue = newInstanceForType(self.ttype)
+ return self.name+" = "+defaultValue
+
+class Scope(object):
+ def __init__(self, parent=None):
+ self.index = 0
+ self.vars = {}
+ self.parent = parent
+ self.childIndex = 0
+ if self.parent:
+ self.level = self.parent.level + 1
+ self.index = self.parent.childIndex
+ self.parent.childIndex+= 1
+ self.suffix = self.parent.suffix+str(self.index)
+ else:
+ self.level = 0
+ self.index = 0
+ self.suffix = ""
+
+ def declare(self, ttype, name):
+ if name in self.vars:
+ raise Exception, "Variable \""+name+"\" already declared in this scope."
+ result = Variable(ttype, "$_"+name+self.suffix)
+ self.vars[name] = result
+ return result
+
+ def get(self, var):
+ if var.name in self.vars:
+ return self.vars[var.name]
+ elif self.parent:
+ return self.parent.get(var)
+ else:
+ raise Exception, "Variable \""+var.name+"\" does not exist in any enclosing scope"
+
+PHP_TYPE_PROTOCOL_MAP = {
+ BOOL_TYPE : "Bool",
+ STRING_TYPE: "String",
+ UTF7_TYPE: "String",
+ UTF8_TYPE: "Wstring",
+ BYTE_TYPE : "Byte",
+ I08_TYPE: "Byte",
+ I16_TYPE: "I16",
+ I32_TYPE: "I32",
+ I64_TYPE: "I64",
+ I16_TYPE: "I16",
+ I32_TYPE: "I32",
+ I64_TYPE: "I64",
+ U08_TYPE: "Byte",
+ U16_TYPE: "U16",
+ U32_TYPE: "U32",
+ U64_TYPE: "U64",
+ U16_TYPE: "U16",
+ U32_TYPE: "U32",
+ U64_TYPE: "U64",
+ FLOAT_TYPE: "Float",
+ DOUBLE_TYPE: "Double"
+ }
+
+PHP_PRIMITIVE_TYPE_IO_METHOD_SUFFIX_MAP = {
+ "void" :"Void",
+ "bool" : "Bool",
+ "string": "String",
+ "utf7": "String",
+ "utf8": "String",
+ "utf16": "String",
+ "i08": "Byte",
+ "i16": "I16",
+ "i32": "I32",
+ "i64": "I64",
+ "u08": "Byte",
+ "u16": "U16",
+ "u32": "U32",
+ "u64": "U64",
+ "float": "Float",
+ "double": "Double"
+}
+
+class CodeGenerator(object):
+ def __init__(self,
+ scope=Scope(),
+ indent = Indenter()):
+ self.scope = scope
+ self.indent = indent
+
+ def newScope(self):
+ self.scope = Scope(self.scope)
+
+ def oldScope(self):
+ if not self.scope.parent:
+ raise Exception, "Unbalanced scope entry/exit"
+
+ self.scope = self.scope.parent
+
+ def declare(self, type, name, defaultValue=None):
+ tvar = self.scope.declare(type, name)
+ return tvar
+
+ def toDeclaration(self, tvar, new=False):
+ if not new:
+ defaultValue = defaultValueForType(tvar.ttype)
+ else:
+ defaultValue = newInstanceForType(tvar.ttype)
+
+ return self.indent()+tvar.name+" = "+defaultValue+";\n"
+
+class Reader(CodeGenerator):
+ def __init__(self,
+ iprot="$this->_iprot",
+ itrans="$this->_itrans",
+ scope=Scope(),
+ indent = Indenter()):
+ CodeGenerator.__init__(self, scope, indent)
+ self.iprot = iprot
+ self.itrans = itrans
+
+ def toReadMessageBegin(self, messageNameVar, seqidVar):
+ return self.indent()+self.iprot+"->readMessageBegin("+self.itrans+", "+messageNameVar.name+", "+seqidVar.name+");\n"
+
+ def toReadMessageEnd(self):
+ return self.indent()+self.iprot+"->readMessageEnd("+self.itrans+");\n"
+
+ def toReadStructBegin(self, structNameVar):
+ return self.indent()+self.iprot+"->readStructBegin("+self.itrans+", "+structNameVar.name+");\n"
+
+ def toReadStructEnd(self):
+ return self.indent()+self.iprot+"->readStructEnd("+self.itrans+");\n"
+
+ def toReadMapBegin(self, keyTypeVar, valueTypeVar, sizeVar):
+ return self.indent()+self.iprot+"->readMapBegin("+self.itrans+", "+keyTypeVar.name+", "+valueTypeVar.name+", "+sizeVar.name+");\n"
+
+ def toReadMapEnd(self):
+ return self.indent()+self.iprot+"->readMapEnd("+self.itrans+");\n"
+
+ def toReadSetBegin(self, valueTypeVar, sizeVar):
+ return self.indent()+self.iprot+"->readSetBegin("+self.itrans+", "+valueTypeVar.name+", "+sizeVar.name+");\n"
+
+ def toReadSetEnd(self):
+ return self.indent()+self.iprot+"->readSetEnd("+self.itrans+");\n"
+
+ def toReadListBegin(self, valueTypeVar, sizeVar):
+ return self.indent()+self.iprot+"->readListBegin("+self.itrans+", "+valueTypeVar.name+", "+sizeVar.name+");\n"
+
+ def toReadListEnd(self):
+ return self.indent()+self.iprot+"->readListEnd("+self.itrans+");\n"
+
+ def toReadFieldBegin(self, fieldNameVar, fieldTypeVar, fieldIdVar):
+ return self.indent()+self.iprot+"->readFieldBegin("+self.itrans+", "+fieldNameVar.name+", "+fieldTypeVar.name+", "+fieldIdVar.name+");\n"
+
+ def toReadFieldEnd(self):
+ return self.indent()+self.iprot+"->readFieldEnd("+self.itrans+");\n"
+
+ def toSkipField(self, fieldTypeVar):
+ return self.indent()+self.iprot+"->skipField("+self.itrans+", "+fieldTypeVar.name+");\n"
+
+ def toReadPrimitive(self, value, suffix):
+ return self.indent()+value+" = "+self.iprot+"->read"+suffix+"("+self.itrans+");\n"
+
+ def toRead(self, value, ttype):
+
+ if isinstance(ttype, PrimitiveType):
+ return self.toReadPrimitive(value, PHP_TYPE_PROTOCOL_MAP[ttype])
+
+ elif isinstance(ttype, StructType):
+ self.newScope()
+ result = self.toReadStruct(value, ttype)
+ self.oldScope()
+ return result
+
+ elif isinstance(ttype, CollectionType):
+ self.newScope()
+ result = self.toReadCollection(value, ttype)
+ self.oldScope()
+ return result
+
+ elif isinstance(ttype, EnumType):
+ return self.toReadPrimitive(value, PHP_TYPE_PROTOCOL_MAP[I32_TYPE])
+
+ elif isinstance(ttype, TypedefType):
+ return self.toRead(value, ttype.definitionType)
+
+ else:
+ raise Exception, "Unsupported type "+str(type(ttype))+":"+str(ttype)
+
+ def toReadStruct(self, value, struct):
+
+ nameVar = self.declare(STRING_TYPE, "name")
+ fieldIdVar = self.declare(I16_TYPE, "fieldId")
+ fieldTypeVar = self.declare(U32_TYPE, "fieldType")
+ localVars = [nameVar, fieldTypeVar, fieldIdVar]
+
+ result= string.join([self.toDeclaration(localVar) for localVar in localVars], "")
+
+ result+= self.toReadStructBegin(nameVar)
+
+ result+= self.indent()+"while(true) {\n"
+
+ self.indent += 1
+
+ result+= self.toReadFieldBegin(nameVar, fieldTypeVar, fieldIdVar)
+
+ result += self.indent()+"if("+fieldTypeVar.name+" == "+PHP_PROTOCOL_TSTOP+") {\n"
+
+ self.indent+= 1
+
+ result+= self.indent()+"break;\n"
+
+ self.indent-= 1
+
+ result+= self.indent()+"}\n"
+
+ result+= self.indent()+"switch("+fieldIdVar.name+") {\n"
+
+ self.indent+= 1
+
+ # Sort field list in order of increasing ids
+
+ fieldList = []
+
+ fieldList+= struct.fieldList
+
+ fieldList.sort(lambda a,b: a.id - b.id)
+
+ for field in fieldList:
+ if isVoidType(field.type):
+ continue
+
+ result+= self.indent()+"case "+str(field.id)+":\n"
+
+ result+= self.toRead(value+"->"+field.name, field.type)
+ result+= self.indent()+"break;\n"
+
+ result+= self.indent()+"default:\n"
+ result+= self.toSkipField(fieldTypeVar)
+ result+= self.indent()+"break;\n"
+
+ self.indent-= 1
+
+ result+= self.indent()+"}\n"
+
+ self.indent-= 1
+
+ result+= self.indent()+"}\n"
+
+ result+= self.toReadStructEnd()
+
+ result+= self.indent()+"return "+value+";\n"
+
+ return result
+
+ def toReadCollection(self, value, collection):
+
+ isMap = isinstance(collection, MapType)
+
+ sizeVar = self.declare(U32_TYPE, "size")
+ valueTypeVar = self.declare(U32_TYPE, "valueType")
+ elemVar = self.declare(collection.valueType, "elem")
+ localVars = [sizeVar, valueTypeVar, elemVar]
+
+ if isMap:
+ keyTypeVar = self.declare(U32_TYPE, "keyType")
+ key = self.declare(collection.keyType, "key")
+ localVars+= [keyTypeVar, key]
+
+ ix = self.declare(U32_TYPE, "ix")
+
+ result= string.join([self.toDeclaration(localVar) for localVar in localVars], "")
+
+ if isMap:
+ result+= self.toReadMapBegin(keyTypeVar, valueTypeVar, sizeVar)
+ elif isinstance(collection, ListType):
+ result+= self.toReadListBegin(valueTypeVar, sizeVar)
+ elif isinstance(collection, SetType):
+ result+= self.toReadSetBegin(valueTypeVar, sizeVar)
+ else:
+ raise Exception, "Unknown collection type "+str(collection)
+
+ result+= self.indent()+"for("+ix.name+" = 0; "+ix.name+" < "+sizeVar.name+"; ++"+ix.name+") {\n"
+
+ self.indent+= 1
+
+ if isMap:
+ result+= self.toRead(key.name, collection.keyType)
+
+ result+= self.toRead(elemVar.name, collection.valueType)
+
+ if isMap:
+ result+= self.indent()+value+"["+key.name+"] = "+elemVar.name+";\n"
+ else:
+ result+= self.indent()+value+"[] = "+elemVar.name+";\n"
+
+ self.indent-= 1
+
+ result+= self.indent()+"}\n"
+
+ if isMap:
+ result+= self.toReadMapEnd()
+ elif isinstance(collection, ListType):
+ result+= self.toReadListEnd()
+ elif isinstance(collection, SetType):
+ result+= self.toReadSetEnd()
+ else:
+ raise Exception, "Unknown collection type "+str(collection)
+
+ return result
+
+class Writer(CodeGenerator):
+ def __init__(self,
+ oprot="$this->_oprot",
+ otrans="$this->_otrans",
+ scope=Scope(),
+ indent=Indenter()):
+ CodeGenerator.__init__(self, scope, indent)
+ self.oprot = oprot
+ self.otrans = otrans
+
+ def toWriteMessageBegin(self, messageName, seqid):
+ return self.indent()+self.oprot+"->writeMessageBegin("+self.otrans+", "+messageName+", "+seqid+");\n"
+
+ def toWriteMessageEnd(self):
+ return self.indent()+self.oprot+"->writeMessageEnd("+self.otrans+");\n"
+
+ def toWriteStructBegin(self, structName):
+ return self.indent()+self.oprot+"->writeStructBegin("+self.otrans+", "+structName+");\n"
+
+ def toWriteStructEnd(self):
+ return self.indent()+self.oprot+"->writeStructEnd("+self.otrans+");\n"
+
+ def toWriteMapBegin(self, keyType, valueType, size):
+ return self.indent()+self.oprot+"->writeMapBegin("+self.otrans+", "+keyType+", "+valueType+", "+size+");\n"
+
+ def toWriteMapEnd(self):
+ return self.indent()+self.oprot+"->writeMapEnd("+self.otrans+");\n"
+
+ def toWriteSetBegin(self, valueType, size):
+ return self.indent()+self.oprot+"->writeSetBegin("+self.otrans+", "+valueType+", "+size+");\n"
+
+ def toWriteSetEnd(self):
+ return self.indent()+self.oprot+"->writeSetEnd("+self.otrans+");\n"
+
+ def toWriteListBegin(self, valueType, size):
+ return self.indent()+self.oprot+"->writeListBegin("+self.otrans+", "+valueType+", "+size+");\n"
+
+ def toWriteListEnd(self):
+ return self.indent()+self.oprot+"->writeListEnd("+self.otrans+");\n"
+
+ def toWriteFieldBegin(self, fieldName, fieldType, fieldId):
+ return self.indent()+self.oprot+"->writeFieldBegin("+self.otrans+", "+fieldName+", "+fieldType+", "+str(fieldId)+");\n"
+
+ def toWriteFieldEnd(self):
+ return self.indent()+self.oprot+"->writeFieldEnd("+self.otrans+");\n"
+
+ def toWriteFieldStop(self):
+ return self.indent()+self.oprot+"->writeFieldStop("+self.otrans+");\n"
+
+ def toSkipField(self, fieldType):
+ return self.indent()+self.oprot+"->skipField("+self.otrans+", "+fieldType+");\n"
+
+ def toWriteFlush(self):
+ return self.indent()+self.otrans+"->flush();\n"
+
+ def toWritePrimitive(self, value, suffix):
+ return self.indent()+self.oprot+"->write"+suffix+"("+self.otrans+", "+value+");\n"
+
+ def toWrite(self, value, ttype):
+
+ if isinstance(ttype, PrimitiveType):
+ return self.toWritePrimitive(value, PHP_TYPE_PROTOCOL_MAP[ttype])
+
+ elif isinstance(ttype, StructType):
+ self.newScope()
+ result = self.toWriteStruct(value, ttype)
+ self.oldScope()
+ return result
+
+ elif isinstance(ttype, CollectionType):
+ self.newScope()
+ result = self.toWriteCollection(value, ttype)
+ self.oldScope()
+ return result
+
+ elif isinstance(ttype, EnumType):
+ return self.toWritePrimitive(value, PHP_TYPE_PROTOCOL_MAP[I32_TYPE])
+
+ elif isinstance(ttype, TypedefType):
+ return self.toWrite(value, ttype.definitionType)
+
+ else:
+ raise Exception, "Unsupported type "+str(type(ttype))+":"+str(ttype)
+
+ def toWriteStruct(self, value, struct):
+
+ result=self.indent()+"{\n"
+
+ self.indent+= 1
+
+ result+= self.toWriteStructBegin("\""+struct.name+"\"")
+
+ for field in struct.fieldList:
+ if isVoidType(field.type):
+ continue
+
+ result+= self.toWriteFieldBegin("\""+field.name+"\"", toWireType(field.type), field.id)
+ result+= self.toWrite(value+"->"+field.name, field.type)
+ result+= self.toWriteFieldEnd()
+
+ result+= self.toWriteFieldStop()
+
+ result+= self.toWriteStructEnd()
+
+ self.indent-= 1
+
+ result+= self.indent()+"}\n"
+
+ return result
+
+ def toWriteCollection(self, value, collection):
+
+ result=self.indent()+"{\n"
+
+ self.indent+= 1
+
+ size = "count("+value+")"
+
+ isMap = isinstance(collection, MapType)
+
+ elemVar = self.declare(VOID_TYPE, "elem")
+
+ if isMap:
+ keyVar = self.declare(VOID_TYPE, "key")
+ result+= self.toWriteMapBegin(toWireType(collection.keyType), toWireType(collection.valueType), size)
+ elif isinstance(collection, ListType):
+ result+= self.toWriteListBegin(toWireType(collection.valueType), size)
+ elif isinstance(collection, SetType):
+ result+= self.toWriteSetBegin(toWireType(collection.valueType), size)
+ else:
+ raise Exception, "Unknown collection type "+str(collection)
+
+ if isMap:
+
+ result+= self.indent()+"forech("+value+" as "+keyVar.name+" => "+elemVar.name+") {\n"
+
+ else:
+
+ result+= self.indent()+"forech("+value+" as "+elemVar.name+") {\n"
+
+ self.indent+= 1
+
+ if isMap:
+ result+= self.toWrite(keyVar.name, collection.keyType)
+
+ result+= self.toWrite(elemVar.name, collection.valueType)
+
+ self.indent-= 1
+
+ result+= self.indent()+"}\n"
+
+ if isMap:
+ result+= self.toWriteMapEnd()
+ elif isinstance(collection, ListType):
+ result+= self.toWriteListEnd()
+ elif isinstance(collection, SetType):
+ result+= self.toWriteSetEnd()
+ else:
+ raise Exception, "Unknown collection type "+str(collection)
+
+ self.indent-= 1
+
+ result+= self.indent()+"}\n"
+
+ return result
+
+class ClientFunctionGenerator(CodeGenerator):
+ def __init__(self,
+ scope=Scope(),
+ indent=Indenter()):
+ CodeGenerator.__init__(self, scope, indent)
+ self.reader = Reader(scope=scope, indent=indent)
+ self.writer = Writer(scope=scope, indent=indent)
+
+ def __call__(self, function):
+
+ result= self.indent()+"public function "+function.name+"("+string.join(["$arg"+str(ix) for ix in range(len(function.args()))], ", ")+") {\n"
+
+ self.newScope()
+
+ self.indent+= 1
+
+ result+= self.writer.toWriteMessageBegin("\""+function.name+"\"", "$this->seqid")
+ result+= self.indent()+"$this->_seqid++;\n"
+
+ # Send the args struct
+
+ result+= self.indent()+"{\n"
+
+ self.newScope()
+
+ self.indent+= 1
+
+ result+= self.writer.toWriteStructBegin("\""+function.argsStruct.name+"\"")
+
+ for ix in range(len(function.argsStruct.fieldList)):
+ field = function.argsStruct.fieldList[ix]
+ if isVoidType(field.type):
+ continue
+
+ result+= self.writer.toWriteFieldBegin("\""+field.name+"\"", toWireType(field.type), field.id)
+ result+= self.writer.toWrite("$arg"+str(ix), field.type)
+ result+= self.writer.toWriteFieldEnd()
+
+ result+= self.writer.toWriteFieldStop()
+
+ result+= self.writer.toWriteStructEnd()
+
+ self.indent-= 1
+
+ self.oldScope()
+
+ result+= self.indent()+"}\n"
+
+ result+= self.writer.toWriteMessageEnd()
+
+ result+= self.writer.toWriteFlush();
+
+ resultVar = self.declare(function.resultStruct, "result")
+ nameVar = self.declare(function.resultStruct, "name")
+ seqidVar = self.declare(function.resultStruct, "seqid")
+
+ result+= self.toDeclaration(resultVar, True)
+ result+= self.toDeclaration(nameVar)
+ result+= self.toDeclaration(seqidVar)
+
+ result+= self.reader.toReadMessageBegin(nameVar, seqidVar)
+
+ result+= self.indent()+"{\n"
+
+ self.newScope()
+
+ self.indent+= 1
+
+ result+= self.reader.toRead(resultVar.name, function.resultStruct)
+
+ self.indent-= 1
+
+ self.oldScope()
+
+ result+= self.indent()+"}\n"
+
+ self.indent-= 1
+
+ self.oldScope()
+
+ result+= self.reader.toReadMessageEnd()
+
+ result+= self.indent()+"}\n"
+
+ return result
+
+class ClientServiceGenerator(CodeGenerator):
+ def __init__(self,
+ scope=Scope(),
+ indent=Indenter()):
+ CodeGenerator.__init__(self, scope, indent)
+ self.functionGenerator = ClientFunctionGenerator(scope, indent)
+
+ def __call__(self, service):
+
+ result= self.indent()+"class "+service.name+"Client extends "+service.name+"If {\n"
+
+ self.indent+= 1
+
+ result+= self.indent()+"private $_itrans = null;\n"
+ result+= self.indent()+"private $_otrans = null;\n\n"
+
+ result+= self.indent()+"private $_iprot = null;\n"
+ result+= self.indent()+"private $_oprot = null;\n\n"
+ result+= self.indent()+"private $_seqid = 0;\n\n"
+
+ result+= self.indent()+"public function __construct() {\n"
+ self.indent+= 1
+
+ result+= self.indent()+"$argv = func_get_args();\n"
+ result+= self.indent()+"$argc = count($argv);\n"
+ result+= self.indent()+"if ($argc == 2) {;\n"
+ self.indent+= 1
+ result+= self.indent()+"$this->_itrans = $this->_otrans = $argv[0];\n"
+ result+= self.indent()+"$this->_iprot = $this->_oprot = $argv[1];\n"
+ self.indent-= 1
+ result+= self.indent()+"} else if($argc == 4) {\n"
+ self.indent+= 1
+ result+= self.indent()+"$this->_itrans = $argv[0];\n"
+ result+= self.indent()+"$this->_otrans = $argv[1];\n"
+ result+= self.indent()+"$this->_iprot = $argv[2];\n"
+ result+= self.indent()+"$this->_oprot = $argv[3];\n"
+ self.indent-= 1
+ result+= self.indent()+"}\n"
+ self.indent-= 1
+ result+= self.indent()+"}\n\n"
+
+ for function in service.functionList:
+
+ result+= self.functionGenerator(function)
+
+ self.indent-= 1
+ result+= self.indent()+"}\n"
+
+ return result
+
+PHP_PRIMITIVE_DEFAULT_MAP = {
+ VOID_TYPE : "",
+ BOOL_TYPE : "False",
+ STRING_TYPE: "\'\'",
+ UTF7_TYPE: "\'\'",
+ UTF8_TYPE: "\'\'",
+ UTF16_TYPE: "\'\'",
+ BYTE_TYPE : "0",
+ I08_TYPE: "0",
+ I16_TYPE: "0",
+ I32_TYPE: "0",
+ I64_TYPE: "0",
+ U08_TYPE: "0",
+ U16_TYPE: "0",
+ U32_TYPE: "0",
+ U64_TYPE: "0",
+ FLOAT_TYPE: "0.0",
+ DOUBLE_TYPE: "0.0"
+}
+
+def toPHPNamespacePrefix(namespace):
+ """No namespaces in PHP"""
+ return ""
+
+def toPHPNamespaceSuffix(namespace):
+ """No namespaces in PHP"""
+ return ""
+
+def defaultValueForType(ttype, value=None):
+ """Returns the default literal value for a given type"""
+
+ if value:
+ return value
+ elif isinstance(ttype, PrimitiveType):
+ return PHP_PRIMITIVE_DEFAULT_MAP[ttype]
+ elif isinstance(ttype, CollectionType):
+ return "array()"
+ elif isinstance(ttype, StructType):
+ return "null"
+ elif isinstance(ttype, EnumType):
+ return "0"
+ elif isinstance(ttype, TypedefType):
+ return defaultValueForType(toCanonicalType(ttype))
+ else:
+ raise Exception, "Unknown type "+str(ttype)
+
+def newInstanceForType(ttype, value=None):
+ """Returns the default new instance for a given type"""
+
+ if value:
+ return value
+ elif isinstance(ttype, StructType):
+ return "new "+ttype.name+"()"
+ else:
+ return defaultValueForType(ttype, value)
+
+def toPHPTypeDeclaration(ttype):
+ """ Converts the thrift IDL type to the corresponding PHP type. Note that if ttype is FieldType, this function
+converts to type declaration followed by field name, i.e. \"string string_thing\""""
+
+ if isinstance(ttype, Field):
+ return "$"+ttype.name
+ if isinstance(ttype, Function):
+ return ttype.name+"("+string.join([toPHPTypeDeclaration(arg) for arg in ttype.args()], ", ")+")"
+ else:
+ return ""
+
+def toTypedefDefinition(typedef):
+ """Converts a thrift typedef to a PHP typdef definition."""
+
+ return ""
+
+def toEnumDefinition(enum):
+ """ Converts a thrift enum to a PHP enum """
+
+ result = "$GLOBALS[\'E_"+enum.name+"\'] = array(\n"
+
+ result += string.join([" \'"+ed.name+"\' => "+str(ed.id)+",\n" for ed in enum.enumDefs], "")
+
+ result+= ");\n\n"
+
+ result += "final class "+enum.name+" {\n"
+
+ result += string.join([" const "+ed.name+" = "+str(ed.id)+";\n" for ed in enum.enumDefs], "")
+
+ result += "}\n"
+
+ return result
+
+def toStructDefinition(struct):
+ """Converts a thrift struct to a PHP class"""
+
+ result = "class "+struct.name+" {\n"
+
+ # Create constructor defaults for primitive types
+
+ # Field declarations
+
+ result+= string.join([" public $"+field.name+" = "+defaultValueForType(field.type)+";\n" for field in struct.fieldList if not isVoidType(field.type)], "")
+
+ # bring it on home
+
+ result+= "}\n"
+
+ return result
+
+PHP_DEFINITION_MAP = {
+ TypedefType : toTypedefDefinition,
+ EnumType : toEnumDefinition,
+ StructType : toStructDefinition,
+ ExceptionType : toStructDefinition,
+ Service : None
+ }
+
+def toDefinitions(definitions):
+ """Converts an arbitrary thrift grammatical unit definition to the corresponding PHP definition"""
+
+ result = ""
+
+ for definition in definitions:
+
+ writer = PHP_DEFINITION_MAP[type(definition)]
+
+ if writer:
+ result+= writer(definition)+"\n"
+
+ return result
+
+PHP_THRIFT_NS = "facebook::thrift"
+
+PHP_INTERFACE_FUNCTION_DECLARATION = Template(""" public abstract function ${functionDeclaration};
+""")
+
+PHP_INTERFACE_DECLARATION = Template("""
+abstract class ${service}If {
+${functionDeclarations}};
+""")
+
+def toServiceInterfaceDeclaration(service, debugp=None):
+ """Converts a thrift service definition into a C++ abstract base class"""
+
+ functionDeclarations = string.join([PHP_INTERFACE_FUNCTION_DECLARATION.substitute(service=service.name, functionDeclaration=toPHPTypeDeclaration(function)) for function in service.functionList], "")
+
+ return PHP_INTERFACE_DECLARATION.substitute(service=service.name, functionDeclarations=functionDeclarations)
+
+PHP_EXCEPTION = PHP_THRIFT_NS+"::Exception"
+
+PHP_SP = Template("boost::shared_ptr<${klass}> ")
+
+
+PHP_PROTOCOL_TSTOP = "TType::STOP"
+PHP_PROTOCOL_TTYPE = "TType::"
+PHP_PROTOCOL_CALL = "TMessageType::T_CALL"
+PHP_PROTOCOL_REPLY = "TMessageType::T_REPLY"
+
+PHP_TTYPE_MAP = {
+ STOP_TYPE : PHP_PROTOCOL_TTYPE+"STOP",
+ VOID_TYPE : PHP_PROTOCOL_TTYPE+"VOID",
+ BOOL_TYPE : PHP_PROTOCOL_TTYPE+"BOOL",
+ UTF7_TYPE : PHP_PROTOCOL_TTYPE+"UTF7",
+ UTF7_TYPE : PHP_PROTOCOL_TTYPE+"UTF7",
+ UTF8_TYPE : PHP_PROTOCOL_TTYPE+"UTF8",
+ UTF16_TYPE : PHP_PROTOCOL_TTYPE+"UTF16",
+ U08_TYPE : PHP_PROTOCOL_TTYPE+"U08",
+ I08_TYPE : PHP_PROTOCOL_TTYPE+"I08",
+ I16_TYPE : PHP_PROTOCOL_TTYPE+"I16",
+ I32_TYPE : PHP_PROTOCOL_TTYPE+"I32",
+ I64_TYPE : PHP_PROTOCOL_TTYPE+"I64",
+ U08_TYPE : PHP_PROTOCOL_TTYPE+"U08",
+ U16_TYPE : PHP_PROTOCOL_TTYPE+"U16",
+ U32_TYPE : PHP_PROTOCOL_TTYPE+"U32",
+ U64_TYPE : PHP_PROTOCOL_TTYPE+"U64",
+ FLOAT_TYPE : PHP_PROTOCOL_TTYPE+"FLOAT",
+ DOUBLE_TYPE : PHP_PROTOCOL_TTYPE+"DOUBLE",
+ StructType : PHP_PROTOCOL_TTYPE+"STRUCT",
+ ExceptionType : PHP_PROTOCOL_TTYPE+"STRUCT",
+ ListType : PHP_PROTOCOL_TTYPE+"LIST",
+ MapType : PHP_PROTOCOL_TTYPE+"MAP",
+ SetType : PHP_PROTOCOL_TTYPE+"SET"
+}
+
+
+def toWireType(ttype):
+ """Converts a thrift type to the corresponding wire type. This differs from toPHPTypeDeclaration in that it reduces typedefs
+to their canonical form and converts enums to signedf 32 bit integers"""
+
+ if isinstance(ttype, PrimitiveType):
+ return PHP_TTYPE_MAP[ttype]
+
+ elif isinstance(ttype, EnumType):
+ return PHP_TTYPE_MAP[I32_TYPE]
+
+ elif isinstance(ttype, TypedefType):
+ return toWireType(toCanonicalType(ttype))
+
+ elif isinstance(ttype, StructType) or isinstance(ttype, CollectionType):
+ return PHP_TTYPE_MAP[type(ttype)]
+
+ else:
+ raise Exception, "No wire type for thrift type: "+str(ttype)
+
+def toGenDir(filename, suffix="php-gen", debugp=None):
+ """creates a generated-code subdirectory for C++ code based on filename of thrift source file and optional suffix"""
+
+ result = os.path.join(os.path.split(filename)[0], suffix)
+
+ if not os.path.exists(result):
+ os.mkdir(result)
+
+ return result
+
+def toBasename(filename, debugp=None):
+ """ Take the filename minus the path and\".thrift\" extension if present """
+
+ basename = os.path.split(filename)[1]
+
+ tokens = os.path.splitext(basename)
+
+ if tokens[1].lower() == ".thrift":
+ basename = tokens[0]
+
+ if debugp:
+ debugp("toBasename("+str(filename)+") => "+str(basename))
+
+ return basename
+
+def toDefinitionHeaderName(filename, genDir=None, debugp=None):
+ """Creates a file name for the public thrift data types based on filename of thrift source file and optional suffix"""
+
+ if not genDir:
+ genDir = toGenDir(filename)
+
+ basename = toBasename(filename)
+
+ result = os.path.join(genDir, basename+"_types.php")
+
+ if debugp:
+ debugp("toDefinitionHeaderName("+str(filename)+", "+str(genDir)+") => "+str(basename))
+
+ return result
+
+def writeDefinitionHeader(program, filename, genDir=None, debugp=None):
+ """Writes public thrift data types defined in program into a public data types header file. Uses the name of the original
+thrift source, filename, and the optional generated C++ code directory, genDir, to determine the name and location of header file"""
+
+ definitionHeader = toDefinitionHeaderName(filename, genDir)
+
+ if debugp:
+ debugp("definitionHeader: "+str(definitionHeader))
+
+ phpFile = file(definitionHeader, "w")
+
+ basename = toBasename(filename)
+
+ phpFile.write(PHP_TYPES_HEADER.substitute(source=basename, date=time.ctime(), namespacePrefix=toPHPNamespacePrefix(program.namespace)))
+
+ phpFile.write(toDefinitions(program.definitions))
+
+ phpFile.write(PHP_TYPES_FOOTER.substitute(source=basename, namespaceSuffix=toPHPNamespaceSuffix(program.namespace)))
+
+ phpFile.close()
+
+def toToImplementationSourceName(filename, genDir=None, debugp=None):
+ """Creates a file name for the public thrift data types based on filename of thrift source file and optional suffix"""
+
+ if not genDir:
+ genDir = toGenDir(filename)
+
+ basename = toBasename(filename)
+
+ result = os.path.join(genDir, basename+".php")
+
+ if debugp:
+ debugp("toToImplementationSourceName("+str(filename)+", "+str(genDir)+") => "+str(basename))
+
+ return result
+
+def writeImplementationSource(program, filename, genDir=None, debugp=None):
+ """Writes public thrift service interface, client stubs, and private types defined in a thrift program into a php library file. Uses the name of the original
+thrift source, filename, and the optional generated C++ code directory, genDir, to determine the name and location of header file"""
+
+ toImplementationSource = toToImplementationSourceName(filename, genDir)
+
+ if debugp:
+ debugp("toImplementationSource: "+str(toImplementationSource))
+
+ phpFile = file(toImplementationSource, "w")
+
+ basename = toBasename(filename)
+
+ phpFile.write(PHP_SERVICES_HEADER.substitute(source=basename, date=time.ctime(), namespacePrefix=toPHPNamespacePrefix(program.namespace)))
+
+ # Generate classes for function result "structs"
+
+ privateStructs = []
+
+ for service in program.serviceMap.values():
+
+ phpFile.write(toServiceInterfaceDeclaration(service))
+
+ for function in service.functionList:
+ privateStructs.append(function.resultStruct)
+
+ phpFile.write(toDefinitions(privateStructs))
+
+ serviceGenerator = ClientServiceGenerator()
+
+ for service in program.serviceMap.values():
+
+ phpFile.write(serviceGenerator(service))
+
+ phpFile.write(PHP_SERVICES_FOOTER.substitute(source=basename, namespaceSuffix=toPHPNamespaceSuffix(program.namespace)))
+
+ phpFile.close()
+
+class PHPGenerator(Generator):
+
+ def __call__(self, program, filename, genDir=None, debugp=None):
+
+ writeDefinitionHeader(program, filename, genDir, debugp)
+
+ writeImplementationSource(program, filename, genDir, debugp)