blob: 4ad990cac44e41917479513c0189f2777363394f [file] [log] [blame]
""" 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 ${prefix}.'thrift/Thrift.php\';
""")
PHP_TYPES_FOOTER = Template("""
?>
""")
PHP_SERVICES_HEADER = Template(HEADER_COMMENT+"""
require_once dirname(__FILE__).\'/${source}_types.php\';
require_once ${prefix}.'thrift/protocol/TType.php';
require_once ${prefix}.'thrift/protocol/TProtocol.php';
require_once ${prefix}.'thrift/transport/TTransport.php';
""")
PHP_SERVICES_FOOTER = Template("""
?>
""")
PHP_IMPL_HEADER = Template(HEADER_COMMENT+"""
require_once ${prefix}.'thrift/Thrift.php';
require_once ${prefix}.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, messageTypeVar, seqidVar):
return self.indent()+self.iprot+"->readMessageBegin("+self.itrans+", "+messageNameVar.name+", "+messageTypeVar.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+"->skip("+self.itrans+", "+fieldTypeVar.name+");\n"
def toReadPrimitive(self, value, suffix):
return self.indent()+self.iprot+"->read"+suffix+"("+self.itrans+", "+value+");\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()
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, type, seqid):
return self.indent()+self.oprot+"->writeMessageBegin("+self.otrans+", "+messageName+", "+type+", "+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()+"foreach("+value+" as "+keyVar.name+" => "+elemVar.name+") {\n"
else:
result+= self.indent()+"foreach("+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+"\"", PHP_PROTOCOL_CALL, "$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(STRING_TYPE, "name")
typeVar = self.declare(U32_TYPE, "type")
seqidVar = self.declare(U32_TYPE, "seqid")
result+= self.toDeclaration(resultVar, True)
result+= self.toDeclaration(nameVar)
result+= self.toDeclaration(typeVar)
result+= self.toDeclaration(seqidVar)
result+= self.reader.toReadMessageBegin(nameVar, typeVar, 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"
result+= self.reader.toReadMessageEnd()
if not isVoidType(function.returnType()):
result+= self.indent()+"return "+resultVar.name+"->success;\n"
else:
result+= self.indent()+"return;\n"
self.indent-= 1
self.oldScope()
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::CALL"
PHP_PROTOCOL_REPLY = "TMessageType::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+"LST",
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="gen-php", 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(prefix=PREFIX, 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(prefix=PREFIX, 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)