| """ 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) |