blob: e68a4f73ec5aadd266a8b0b64df9646841419592 [file] [log] [blame]
Marc Slemkoc6936402006-08-23 02:15:31 +00001""" Thrift IDL PHP client stub generator
2
3 This generated PHP class definitions and client stubs for a valid thrift IDL definition
4
5 Author(s): Mark Slee(mclee@facebook.com), Marc Kwiatkowski (marc@facebook.com)
6
7 $Id:
8"""
9import time
10
11import os
12import os.path
13from string import Template
14from thrift.parser import *
15from thrift.generator import *
16
17HEADER_COMMENT = """<?php
18/**
19 * Autogenerated by Thrift
20 * ${date}
21 *
22 * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
23 */
24 """
25
26PHP_TYPES_HEADER = Template(HEADER_COMMENT+"""
27require_once THRIFT_ROOT.\'/Thrift.php\';
28""")
29
30PHP_TYPES_FOOTER = Template("""
31?>
32""")
33
34PHP_SERVICES_HEADER = Template(HEADER_COMMENT+"""
35
36require_once THRIFT_ROOT.'/Thrift.php';
37require_once dirname(__FILE__).\'/${source}_types.php\';
38
39""")
40
41PHP_SERVICES_FOOTER = Template("""
42?>
43""")
44
45PHP_IMPL_HEADER = Template(HEADER_COMMENT+"""
46
47require_once THRIFT_ROOT.'/Thrift.php';
48require_once dirname(__FILE__).\'/${source}_types.php\';
49
50""")
51
52PHP_IMPL_FOOTER = Template("""
53?>
54""")
55
56def php_debug(arg):
57 print(arg)
58
59class Indenter(object):
60 def __init__(self, margin=4):
61 self.margin = ''.join([' ' for ix in range(margin)])
62 self.level = 0
63
64 def __getitem__(self, level):
65 return ''.join([self.margin for ix in range(level)])
66
67 def __iadd__(self, value):
68 self.level = self.level + value
69 return self
70
71 def __isub__(self, value):
72 self.level = self.level - value
73 return self
74
75 def __call__(self):
76 return self.__getitem__(self.level)
77
78class Variable(object):
79 def __init__(self, ttype, name):
80 self.ttype = ttype
81 self.name = name
82
83 def toDeclaration(self, new=False):
84 if not new:
85 defaultValue = defaultValueForType(self.ttype)
86 else:
87 defaultValue = newInstanceForType(self.ttype)
88 return self.name+" = "+defaultValue
89
90class Scope(object):
91 def __init__(self, parent=None):
92 self.index = 0
93 self.vars = {}
94 self.parent = parent
95 self.childIndex = 0
96 if self.parent:
97 self.level = self.parent.level + 1
98 self.index = self.parent.childIndex
99 self.parent.childIndex+= 1
100 self.suffix = self.parent.suffix+str(self.index)
101 else:
102 self.level = 0
103 self.index = 0
104 self.suffix = ""
105
106 def declare(self, ttype, name):
107 if name in self.vars:
108 raise Exception, "Variable \""+name+"\" already declared in this scope."
109 result = Variable(ttype, "$_"+name+self.suffix)
110 self.vars[name] = result
111 return result
112
113 def get(self, var):
114 if var.name in self.vars:
115 return self.vars[var.name]
116 elif self.parent:
117 return self.parent.get(var)
118 else:
119 raise Exception, "Variable \""+var.name+"\" does not exist in any enclosing scope"
120
121PHP_TYPE_PROTOCOL_MAP = {
122 BOOL_TYPE : "Bool",
123 STRING_TYPE: "String",
124 UTF7_TYPE: "String",
125 UTF8_TYPE: "Wstring",
126 BYTE_TYPE : "Byte",
127 I08_TYPE: "Byte",
128 I16_TYPE: "I16",
129 I32_TYPE: "I32",
130 I64_TYPE: "I64",
131 I16_TYPE: "I16",
132 I32_TYPE: "I32",
133 I64_TYPE: "I64",
134 U08_TYPE: "Byte",
135 U16_TYPE: "U16",
136 U32_TYPE: "U32",
137 U64_TYPE: "U64",
138 U16_TYPE: "U16",
139 U32_TYPE: "U32",
140 U64_TYPE: "U64",
141 FLOAT_TYPE: "Float",
142 DOUBLE_TYPE: "Double"
143 }
144
145PHP_PRIMITIVE_TYPE_IO_METHOD_SUFFIX_MAP = {
146 "void" :"Void",
147 "bool" : "Bool",
148 "string": "String",
149 "utf7": "String",
150 "utf8": "String",
151 "utf16": "String",
152 "i08": "Byte",
153 "i16": "I16",
154 "i32": "I32",
155 "i64": "I64",
156 "u08": "Byte",
157 "u16": "U16",
158 "u32": "U32",
159 "u64": "U64",
160 "float": "Float",
161 "double": "Double"
162}
163
164class CodeGenerator(object):
165 def __init__(self,
166 scope=Scope(),
167 indent = Indenter()):
168 self.scope = scope
169 self.indent = indent
170
171 def newScope(self):
172 self.scope = Scope(self.scope)
173
174 def oldScope(self):
175 if not self.scope.parent:
176 raise Exception, "Unbalanced scope entry/exit"
177
178 self.scope = self.scope.parent
179
180 def declare(self, type, name, defaultValue=None):
181 tvar = self.scope.declare(type, name)
182 return tvar
183
184 def toDeclaration(self, tvar, new=False):
185 if not new:
186 defaultValue = defaultValueForType(tvar.ttype)
187 else:
188 defaultValue = newInstanceForType(tvar.ttype)
189
190 return self.indent()+tvar.name+" = "+defaultValue+";\n"
191
192class Reader(CodeGenerator):
193 def __init__(self,
194 iprot="$this->_iprot",
195 itrans="$this->_itrans",
196 scope=Scope(),
197 indent = Indenter()):
198 CodeGenerator.__init__(self, scope, indent)
199 self.iprot = iprot
200 self.itrans = itrans
201
202 def toReadMessageBegin(self, messageNameVar, seqidVar):
203 return self.indent()+self.iprot+"->readMessageBegin("+self.itrans+", "+messageNameVar.name+", "+seqidVar.name+");\n"
204
205 def toReadMessageEnd(self):
206 return self.indent()+self.iprot+"->readMessageEnd("+self.itrans+");\n"
207
208 def toReadStructBegin(self, structNameVar):
209 return self.indent()+self.iprot+"->readStructBegin("+self.itrans+", "+structNameVar.name+");\n"
210
211 def toReadStructEnd(self):
212 return self.indent()+self.iprot+"->readStructEnd("+self.itrans+");\n"
213
214 def toReadMapBegin(self, keyTypeVar, valueTypeVar, sizeVar):
215 return self.indent()+self.iprot+"->readMapBegin("+self.itrans+", "+keyTypeVar.name+", "+valueTypeVar.name+", "+sizeVar.name+");\n"
216
217 def toReadMapEnd(self):
218 return self.indent()+self.iprot+"->readMapEnd("+self.itrans+");\n"
219
220 def toReadSetBegin(self, valueTypeVar, sizeVar):
221 return self.indent()+self.iprot+"->readSetBegin("+self.itrans+", "+valueTypeVar.name+", "+sizeVar.name+");\n"
222
223 def toReadSetEnd(self):
224 return self.indent()+self.iprot+"->readSetEnd("+self.itrans+");\n"
225
226 def toReadListBegin(self, valueTypeVar, sizeVar):
227 return self.indent()+self.iprot+"->readListBegin("+self.itrans+", "+valueTypeVar.name+", "+sizeVar.name+");\n"
228
229 def toReadListEnd(self):
230 return self.indent()+self.iprot+"->readListEnd("+self.itrans+");\n"
231
232 def toReadFieldBegin(self, fieldNameVar, fieldTypeVar, fieldIdVar):
233 return self.indent()+self.iprot+"->readFieldBegin("+self.itrans+", "+fieldNameVar.name+", "+fieldTypeVar.name+", "+fieldIdVar.name+");\n"
234
235 def toReadFieldEnd(self):
236 return self.indent()+self.iprot+"->readFieldEnd("+self.itrans+");\n"
237
238 def toSkipField(self, fieldTypeVar):
239 return self.indent()+self.iprot+"->skipField("+self.itrans+", "+fieldTypeVar.name+");\n"
240
241 def toReadPrimitive(self, value, suffix):
242 return self.indent()+value+" = "+self.iprot+"->read"+suffix+"("+self.itrans+");\n"
243
244 def toRead(self, value, ttype):
245
246 if isinstance(ttype, PrimitiveType):
247 return self.toReadPrimitive(value, PHP_TYPE_PROTOCOL_MAP[ttype])
248
249 elif isinstance(ttype, StructType):
250 self.newScope()
251 result = self.toReadStruct(value, ttype)
252 self.oldScope()
253 return result
254
255 elif isinstance(ttype, CollectionType):
256 self.newScope()
257 result = self.toReadCollection(value, ttype)
258 self.oldScope()
259 return result
260
261 elif isinstance(ttype, EnumType):
262 return self.toReadPrimitive(value, PHP_TYPE_PROTOCOL_MAP[I32_TYPE])
263
264 elif isinstance(ttype, TypedefType):
265 return self.toRead(value, ttype.definitionType)
266
267 else:
268 raise Exception, "Unsupported type "+str(type(ttype))+":"+str(ttype)
269
270 def toReadStruct(self, value, struct):
271
272 nameVar = self.declare(STRING_TYPE, "name")
273 fieldIdVar = self.declare(I16_TYPE, "fieldId")
274 fieldTypeVar = self.declare(U32_TYPE, "fieldType")
275 localVars = [nameVar, fieldTypeVar, fieldIdVar]
276
277 result= string.join([self.toDeclaration(localVar) for localVar in localVars], "")
278
279 result+= self.toReadStructBegin(nameVar)
280
281 result+= self.indent()+"while(true) {\n"
282
283 self.indent += 1
284
285 result+= self.toReadFieldBegin(nameVar, fieldTypeVar, fieldIdVar)
286
287 result += self.indent()+"if("+fieldTypeVar.name+" == "+PHP_PROTOCOL_TSTOP+") {\n"
288
289 self.indent+= 1
290
291 result+= self.indent()+"break;\n"
292
293 self.indent-= 1
294
295 result+= self.indent()+"}\n"
296
297 result+= self.indent()+"switch("+fieldIdVar.name+") {\n"
298
299 self.indent+= 1
300
301 # Sort field list in order of increasing ids
302
303 fieldList = []
304
305 fieldList+= struct.fieldList
306
307 fieldList.sort(lambda a,b: a.id - b.id)
308
309 for field in fieldList:
310 if isVoidType(field.type):
311 continue
312
313 result+= self.indent()+"case "+str(field.id)+":\n"
314
315 result+= self.toRead(value+"->"+field.name, field.type)
316 result+= self.indent()+"break;\n"
317
318 result+= self.indent()+"default:\n"
319 result+= self.toSkipField(fieldTypeVar)
320 result+= self.indent()+"break;\n"
321
322 self.indent-= 1
323
324 result+= self.indent()+"}\n"
325
326 self.indent-= 1
327
328 result+= self.indent()+"}\n"
329
330 result+= self.toReadStructEnd()
331
332 result+= self.indent()+"return "+value+";\n"
333
334 return result
335
336 def toReadCollection(self, value, collection):
337
338 isMap = isinstance(collection, MapType)
339
340 sizeVar = self.declare(U32_TYPE, "size")
341 valueTypeVar = self.declare(U32_TYPE, "valueType")
342 elemVar = self.declare(collection.valueType, "elem")
343 localVars = [sizeVar, valueTypeVar, elemVar]
344
345 if isMap:
346 keyTypeVar = self.declare(U32_TYPE, "keyType")
347 key = self.declare(collection.keyType, "key")
348 localVars+= [keyTypeVar, key]
349
350 ix = self.declare(U32_TYPE, "ix")
351
352 result= string.join([self.toDeclaration(localVar) for localVar in localVars], "")
353
354 if isMap:
355 result+= self.toReadMapBegin(keyTypeVar, valueTypeVar, sizeVar)
356 elif isinstance(collection, ListType):
357 result+= self.toReadListBegin(valueTypeVar, sizeVar)
358 elif isinstance(collection, SetType):
359 result+= self.toReadSetBegin(valueTypeVar, sizeVar)
360 else:
361 raise Exception, "Unknown collection type "+str(collection)
362
363 result+= self.indent()+"for("+ix.name+" = 0; "+ix.name+" < "+sizeVar.name+"; ++"+ix.name+") {\n"
364
365 self.indent+= 1
366
367 if isMap:
368 result+= self.toRead(key.name, collection.keyType)
369
370 result+= self.toRead(elemVar.name, collection.valueType)
371
372 if isMap:
373 result+= self.indent()+value+"["+key.name+"] = "+elemVar.name+";\n"
374 else:
375 result+= self.indent()+value+"[] = "+elemVar.name+";\n"
376
377 self.indent-= 1
378
379 result+= self.indent()+"}\n"
380
381 if isMap:
382 result+= self.toReadMapEnd()
383 elif isinstance(collection, ListType):
384 result+= self.toReadListEnd()
385 elif isinstance(collection, SetType):
386 result+= self.toReadSetEnd()
387 else:
388 raise Exception, "Unknown collection type "+str(collection)
389
390 return result
391
392class Writer(CodeGenerator):
393 def __init__(self,
394 oprot="$this->_oprot",
395 otrans="$this->_otrans",
396 scope=Scope(),
397 indent=Indenter()):
398 CodeGenerator.__init__(self, scope, indent)
399 self.oprot = oprot
400 self.otrans = otrans
401
402 def toWriteMessageBegin(self, messageName, seqid):
403 return self.indent()+self.oprot+"->writeMessageBegin("+self.otrans+", "+messageName+", "+seqid+");\n"
404
405 def toWriteMessageEnd(self):
406 return self.indent()+self.oprot+"->writeMessageEnd("+self.otrans+");\n"
407
408 def toWriteStructBegin(self, structName):
409 return self.indent()+self.oprot+"->writeStructBegin("+self.otrans+", "+structName+");\n"
410
411 def toWriteStructEnd(self):
412 return self.indent()+self.oprot+"->writeStructEnd("+self.otrans+");\n"
413
414 def toWriteMapBegin(self, keyType, valueType, size):
415 return self.indent()+self.oprot+"->writeMapBegin("+self.otrans+", "+keyType+", "+valueType+", "+size+");\n"
416
417 def toWriteMapEnd(self):
418 return self.indent()+self.oprot+"->writeMapEnd("+self.otrans+");\n"
419
420 def toWriteSetBegin(self, valueType, size):
421 return self.indent()+self.oprot+"->writeSetBegin("+self.otrans+", "+valueType+", "+size+");\n"
422
423 def toWriteSetEnd(self):
424 return self.indent()+self.oprot+"->writeSetEnd("+self.otrans+");\n"
425
426 def toWriteListBegin(self, valueType, size):
427 return self.indent()+self.oprot+"->writeListBegin("+self.otrans+", "+valueType+", "+size+");\n"
428
429 def toWriteListEnd(self):
430 return self.indent()+self.oprot+"->writeListEnd("+self.otrans+");\n"
431
432 def toWriteFieldBegin(self, fieldName, fieldType, fieldId):
433 return self.indent()+self.oprot+"->writeFieldBegin("+self.otrans+", "+fieldName+", "+fieldType+", "+str(fieldId)+");\n"
434
435 def toWriteFieldEnd(self):
436 return self.indent()+self.oprot+"->writeFieldEnd("+self.otrans+");\n"
437
438 def toWriteFieldStop(self):
439 return self.indent()+self.oprot+"->writeFieldStop("+self.otrans+");\n"
440
441 def toSkipField(self, fieldType):
442 return self.indent()+self.oprot+"->skipField("+self.otrans+", "+fieldType+");\n"
443
444 def toWriteFlush(self):
445 return self.indent()+self.otrans+"->flush();\n"
446
447 def toWritePrimitive(self, value, suffix):
448 return self.indent()+self.oprot+"->write"+suffix+"("+self.otrans+", "+value+");\n"
449
450 def toWrite(self, value, ttype):
451
452 if isinstance(ttype, PrimitiveType):
453 return self.toWritePrimitive(value, PHP_TYPE_PROTOCOL_MAP[ttype])
454
455 elif isinstance(ttype, StructType):
456 self.newScope()
457 result = self.toWriteStruct(value, ttype)
458 self.oldScope()
459 return result
460
461 elif isinstance(ttype, CollectionType):
462 self.newScope()
463 result = self.toWriteCollection(value, ttype)
464 self.oldScope()
465 return result
466
467 elif isinstance(ttype, EnumType):
468 return self.toWritePrimitive(value, PHP_TYPE_PROTOCOL_MAP[I32_TYPE])
469
470 elif isinstance(ttype, TypedefType):
471 return self.toWrite(value, ttype.definitionType)
472
473 else:
474 raise Exception, "Unsupported type "+str(type(ttype))+":"+str(ttype)
475
476 def toWriteStruct(self, value, struct):
477
478 result=self.indent()+"{\n"
479
480 self.indent+= 1
481
482 result+= self.toWriteStructBegin("\""+struct.name+"\"")
483
484 for field in struct.fieldList:
485 if isVoidType(field.type):
486 continue
487
488 result+= self.toWriteFieldBegin("\""+field.name+"\"", toWireType(field.type), field.id)
489 result+= self.toWrite(value+"->"+field.name, field.type)
490 result+= self.toWriteFieldEnd()
491
492 result+= self.toWriteFieldStop()
493
494 result+= self.toWriteStructEnd()
495
496 self.indent-= 1
497
498 result+= self.indent()+"}\n"
499
500 return result
501
502 def toWriteCollection(self, value, collection):
503
504 result=self.indent()+"{\n"
505
506 self.indent+= 1
507
508 size = "count("+value+")"
509
510 isMap = isinstance(collection, MapType)
511
512 elemVar = self.declare(VOID_TYPE, "elem")
513
514 if isMap:
515 keyVar = self.declare(VOID_TYPE, "key")
516 result+= self.toWriteMapBegin(toWireType(collection.keyType), toWireType(collection.valueType), size)
517 elif isinstance(collection, ListType):
518 result+= self.toWriteListBegin(toWireType(collection.valueType), size)
519 elif isinstance(collection, SetType):
520 result+= self.toWriteSetBegin(toWireType(collection.valueType), size)
521 else:
522 raise Exception, "Unknown collection type "+str(collection)
523
524 if isMap:
525
526 result+= self.indent()+"forech("+value+" as "+keyVar.name+" => "+elemVar.name+") {\n"
527
528 else:
529
530 result+= self.indent()+"forech("+value+" as "+elemVar.name+") {\n"
531
532 self.indent+= 1
533
534 if isMap:
535 result+= self.toWrite(keyVar.name, collection.keyType)
536
537 result+= self.toWrite(elemVar.name, collection.valueType)
538
539 self.indent-= 1
540
541 result+= self.indent()+"}\n"
542
543 if isMap:
544 result+= self.toWriteMapEnd()
545 elif isinstance(collection, ListType):
546 result+= self.toWriteListEnd()
547 elif isinstance(collection, SetType):
548 result+= self.toWriteSetEnd()
549 else:
550 raise Exception, "Unknown collection type "+str(collection)
551
552 self.indent-= 1
553
554 result+= self.indent()+"}\n"
555
556 return result
557
558class ClientFunctionGenerator(CodeGenerator):
559 def __init__(self,
560 scope=Scope(),
561 indent=Indenter()):
562 CodeGenerator.__init__(self, scope, indent)
563 self.reader = Reader(scope=scope, indent=indent)
564 self.writer = Writer(scope=scope, indent=indent)
565
566 def __call__(self, function):
567
568 result= self.indent()+"public function "+function.name+"("+string.join(["$arg"+str(ix) for ix in range(len(function.args()))], ", ")+") {\n"
569
570 self.newScope()
571
572 self.indent+= 1
573
574 result+= self.writer.toWriteMessageBegin("\""+function.name+"\"", "$this->seqid")
575 result+= self.indent()+"$this->_seqid++;\n"
576
577 # Send the args struct
578
579 result+= self.indent()+"{\n"
580
581 self.newScope()
582
583 self.indent+= 1
584
585 result+= self.writer.toWriteStructBegin("\""+function.argsStruct.name+"\"")
586
587 for ix in range(len(function.argsStruct.fieldList)):
588 field = function.argsStruct.fieldList[ix]
589 if isVoidType(field.type):
590 continue
591
592 result+= self.writer.toWriteFieldBegin("\""+field.name+"\"", toWireType(field.type), field.id)
593 result+= self.writer.toWrite("$arg"+str(ix), field.type)
594 result+= self.writer.toWriteFieldEnd()
595
596 result+= self.writer.toWriteFieldStop()
597
598 result+= self.writer.toWriteStructEnd()
599
600 self.indent-= 1
601
602 self.oldScope()
603
604 result+= self.indent()+"}\n"
605
606 result+= self.writer.toWriteMessageEnd()
607
608 result+= self.writer.toWriteFlush();
609
610 resultVar = self.declare(function.resultStruct, "result")
611 nameVar = self.declare(function.resultStruct, "name")
612 seqidVar = self.declare(function.resultStruct, "seqid")
613
614 result+= self.toDeclaration(resultVar, True)
615 result+= self.toDeclaration(nameVar)
616 result+= self.toDeclaration(seqidVar)
617
618 result+= self.reader.toReadMessageBegin(nameVar, seqidVar)
619
620 result+= self.indent()+"{\n"
621
622 self.newScope()
623
624 self.indent+= 1
625
626 result+= self.reader.toRead(resultVar.name, function.resultStruct)
627
628 self.indent-= 1
629
630 self.oldScope()
631
632 result+= self.indent()+"}\n"
633
634 self.indent-= 1
635
636 self.oldScope()
637
638 result+= self.reader.toReadMessageEnd()
639
640 result+= self.indent()+"}\n"
641
642 return result
643
644class ClientServiceGenerator(CodeGenerator):
645 def __init__(self,
646 scope=Scope(),
647 indent=Indenter()):
648 CodeGenerator.__init__(self, scope, indent)
649 self.functionGenerator = ClientFunctionGenerator(scope, indent)
650
651 def __call__(self, service):
652
653 result= self.indent()+"class "+service.name+"Client extends "+service.name+"If {\n"
654
655 self.indent+= 1
656
657 result+= self.indent()+"private $_itrans = null;\n"
658 result+= self.indent()+"private $_otrans = null;\n\n"
659
660 result+= self.indent()+"private $_iprot = null;\n"
661 result+= self.indent()+"private $_oprot = null;\n\n"
662 result+= self.indent()+"private $_seqid = 0;\n\n"
663
664 result+= self.indent()+"public function __construct() {\n"
665 self.indent+= 1
666
667 result+= self.indent()+"$argv = func_get_args();\n"
668 result+= self.indent()+"$argc = count($argv);\n"
669 result+= self.indent()+"if ($argc == 2) {;\n"
670 self.indent+= 1
671 result+= self.indent()+"$this->_itrans = $this->_otrans = $argv[0];\n"
672 result+= self.indent()+"$this->_iprot = $this->_oprot = $argv[1];\n"
673 self.indent-= 1
674 result+= self.indent()+"} else if($argc == 4) {\n"
675 self.indent+= 1
676 result+= self.indent()+"$this->_itrans = $argv[0];\n"
677 result+= self.indent()+"$this->_otrans = $argv[1];\n"
678 result+= self.indent()+"$this->_iprot = $argv[2];\n"
679 result+= self.indent()+"$this->_oprot = $argv[3];\n"
680 self.indent-= 1
681 result+= self.indent()+"}\n"
682 self.indent-= 1
683 result+= self.indent()+"}\n\n"
684
685 for function in service.functionList:
686
687 result+= self.functionGenerator(function)
688
689 self.indent-= 1
690 result+= self.indent()+"}\n"
691
692 return result
693
694PHP_PRIMITIVE_DEFAULT_MAP = {
695 VOID_TYPE : "",
696 BOOL_TYPE : "False",
697 STRING_TYPE: "\'\'",
698 UTF7_TYPE: "\'\'",
699 UTF8_TYPE: "\'\'",
700 UTF16_TYPE: "\'\'",
701 BYTE_TYPE : "0",
702 I08_TYPE: "0",
703 I16_TYPE: "0",
704 I32_TYPE: "0",
705 I64_TYPE: "0",
706 U08_TYPE: "0",
707 U16_TYPE: "0",
708 U32_TYPE: "0",
709 U64_TYPE: "0",
710 FLOAT_TYPE: "0.0",
711 DOUBLE_TYPE: "0.0"
712}
713
714def toPHPNamespacePrefix(namespace):
715 """No namespaces in PHP"""
716 return ""
717
718def toPHPNamespaceSuffix(namespace):
719 """No namespaces in PHP"""
720 return ""
721
722def defaultValueForType(ttype, value=None):
723 """Returns the default literal value for a given type"""
724
725 if value:
726 return value
727 elif isinstance(ttype, PrimitiveType):
728 return PHP_PRIMITIVE_DEFAULT_MAP[ttype]
729 elif isinstance(ttype, CollectionType):
730 return "array()"
731 elif isinstance(ttype, StructType):
732 return "null"
733 elif isinstance(ttype, EnumType):
734 return "0"
735 elif isinstance(ttype, TypedefType):
736 return defaultValueForType(toCanonicalType(ttype))
737 else:
738 raise Exception, "Unknown type "+str(ttype)
739
740def newInstanceForType(ttype, value=None):
741 """Returns the default new instance for a given type"""
742
743 if value:
744 return value
745 elif isinstance(ttype, StructType):
746 return "new "+ttype.name+"()"
747 else:
748 return defaultValueForType(ttype, value)
749
750def toPHPTypeDeclaration(ttype):
751 """ Converts the thrift IDL type to the corresponding PHP type. Note that if ttype is FieldType, this function
752converts to type declaration followed by field name, i.e. \"string string_thing\""""
753
754 if isinstance(ttype, Field):
755 return "$"+ttype.name
756 if isinstance(ttype, Function):
757 return ttype.name+"("+string.join([toPHPTypeDeclaration(arg) for arg in ttype.args()], ", ")+")"
758 else:
759 return ""
760
761def toTypedefDefinition(typedef):
762 """Converts a thrift typedef to a PHP typdef definition."""
763
764 return ""
765
766def toEnumDefinition(enum):
767 """ Converts a thrift enum to a PHP enum """
768
769 result = "$GLOBALS[\'E_"+enum.name+"\'] = array(\n"
770
771 result += string.join([" \'"+ed.name+"\' => "+str(ed.id)+",\n" for ed in enum.enumDefs], "")
772
773 result+= ");\n\n"
774
775 result += "final class "+enum.name+" {\n"
776
777 result += string.join([" const "+ed.name+" = "+str(ed.id)+";\n" for ed in enum.enumDefs], "")
778
779 result += "}\n"
780
781 return result
782
783def toStructDefinition(struct):
784 """Converts a thrift struct to a PHP class"""
785
786 result = "class "+struct.name+" {\n"
787
788 # Create constructor defaults for primitive types
789
790 # Field declarations
791
792 result+= string.join([" public $"+field.name+" = "+defaultValueForType(field.type)+";\n" for field in struct.fieldList if not isVoidType(field.type)], "")
793
794 # bring it on home
795
796 result+= "}\n"
797
798 return result
799
800PHP_DEFINITION_MAP = {
801 TypedefType : toTypedefDefinition,
802 EnumType : toEnumDefinition,
803 StructType : toStructDefinition,
804 ExceptionType : toStructDefinition,
805 Service : None
806 }
807
808def toDefinitions(definitions):
809 """Converts an arbitrary thrift grammatical unit definition to the corresponding PHP definition"""
810
811 result = ""
812
813 for definition in definitions:
814
815 writer = PHP_DEFINITION_MAP[type(definition)]
816
817 if writer:
818 result+= writer(definition)+"\n"
819
820 return result
821
822PHP_THRIFT_NS = "facebook::thrift"
823
824PHP_INTERFACE_FUNCTION_DECLARATION = Template(""" public abstract function ${functionDeclaration};
825""")
826
827PHP_INTERFACE_DECLARATION = Template("""
828abstract class ${service}If {
829${functionDeclarations}};
830""")
831
832def toServiceInterfaceDeclaration(service, debugp=None):
833 """Converts a thrift service definition into a C++ abstract base class"""
834
835 functionDeclarations = string.join([PHP_INTERFACE_FUNCTION_DECLARATION.substitute(service=service.name, functionDeclaration=toPHPTypeDeclaration(function)) for function in service.functionList], "")
836
837 return PHP_INTERFACE_DECLARATION.substitute(service=service.name, functionDeclarations=functionDeclarations)
838
839PHP_EXCEPTION = PHP_THRIFT_NS+"::Exception"
840
841PHP_SP = Template("boost::shared_ptr<${klass}> ")
842
843
844PHP_PROTOCOL_TSTOP = "TType::STOP"
845PHP_PROTOCOL_TTYPE = "TType::"
846PHP_PROTOCOL_CALL = "TMessageType::T_CALL"
847PHP_PROTOCOL_REPLY = "TMessageType::T_REPLY"
848
849PHP_TTYPE_MAP = {
850 STOP_TYPE : PHP_PROTOCOL_TTYPE+"STOP",
851 VOID_TYPE : PHP_PROTOCOL_TTYPE+"VOID",
852 BOOL_TYPE : PHP_PROTOCOL_TTYPE+"BOOL",
853 UTF7_TYPE : PHP_PROTOCOL_TTYPE+"UTF7",
854 UTF7_TYPE : PHP_PROTOCOL_TTYPE+"UTF7",
855 UTF8_TYPE : PHP_PROTOCOL_TTYPE+"UTF8",
856 UTF16_TYPE : PHP_PROTOCOL_TTYPE+"UTF16",
857 U08_TYPE : PHP_PROTOCOL_TTYPE+"U08",
858 I08_TYPE : PHP_PROTOCOL_TTYPE+"I08",
859 I16_TYPE : PHP_PROTOCOL_TTYPE+"I16",
860 I32_TYPE : PHP_PROTOCOL_TTYPE+"I32",
861 I64_TYPE : PHP_PROTOCOL_TTYPE+"I64",
862 U08_TYPE : PHP_PROTOCOL_TTYPE+"U08",
863 U16_TYPE : PHP_PROTOCOL_TTYPE+"U16",
864 U32_TYPE : PHP_PROTOCOL_TTYPE+"U32",
865 U64_TYPE : PHP_PROTOCOL_TTYPE+"U64",
866 FLOAT_TYPE : PHP_PROTOCOL_TTYPE+"FLOAT",
867 DOUBLE_TYPE : PHP_PROTOCOL_TTYPE+"DOUBLE",
868 StructType : PHP_PROTOCOL_TTYPE+"STRUCT",
869 ExceptionType : PHP_PROTOCOL_TTYPE+"STRUCT",
870 ListType : PHP_PROTOCOL_TTYPE+"LIST",
871 MapType : PHP_PROTOCOL_TTYPE+"MAP",
872 SetType : PHP_PROTOCOL_TTYPE+"SET"
873}
874
875
876def toWireType(ttype):
877 """Converts a thrift type to the corresponding wire type. This differs from toPHPTypeDeclaration in that it reduces typedefs
878to their canonical form and converts enums to signedf 32 bit integers"""
879
880 if isinstance(ttype, PrimitiveType):
881 return PHP_TTYPE_MAP[ttype]
882
883 elif isinstance(ttype, EnumType):
884 return PHP_TTYPE_MAP[I32_TYPE]
885
886 elif isinstance(ttype, TypedefType):
887 return toWireType(toCanonicalType(ttype))
888
889 elif isinstance(ttype, StructType) or isinstance(ttype, CollectionType):
890 return PHP_TTYPE_MAP[type(ttype)]
891
892 else:
893 raise Exception, "No wire type for thrift type: "+str(ttype)
894
895def toGenDir(filename, suffix="php-gen", debugp=None):
896 """creates a generated-code subdirectory for C++ code based on filename of thrift source file and optional suffix"""
897
898 result = os.path.join(os.path.split(filename)[0], suffix)
899
900 if not os.path.exists(result):
901 os.mkdir(result)
902
903 return result
904
905def toBasename(filename, debugp=None):
906 """ Take the filename minus the path and\".thrift\" extension if present """
907
908 basename = os.path.split(filename)[1]
909
910 tokens = os.path.splitext(basename)
911
912 if tokens[1].lower() == ".thrift":
913 basename = tokens[0]
914
915 if debugp:
916 debugp("toBasename("+str(filename)+") => "+str(basename))
917
918 return basename
919
920def toDefinitionHeaderName(filename, genDir=None, debugp=None):
921 """Creates a file name for the public thrift data types based on filename of thrift source file and optional suffix"""
922
923 if not genDir:
924 genDir = toGenDir(filename)
925
926 basename = toBasename(filename)
927
928 result = os.path.join(genDir, basename+"_types.php")
929
930 if debugp:
931 debugp("toDefinitionHeaderName("+str(filename)+", "+str(genDir)+") => "+str(basename))
932
933 return result
934
935def writeDefinitionHeader(program, filename, genDir=None, debugp=None):
936 """Writes public thrift data types defined in program into a public data types header file. Uses the name of the original
937thrift source, filename, and the optional generated C++ code directory, genDir, to determine the name and location of header file"""
938
939 definitionHeader = toDefinitionHeaderName(filename, genDir)
940
941 if debugp:
942 debugp("definitionHeader: "+str(definitionHeader))
943
944 phpFile = file(definitionHeader, "w")
945
946 basename = toBasename(filename)
947
948 phpFile.write(PHP_TYPES_HEADER.substitute(source=basename, date=time.ctime(), namespacePrefix=toPHPNamespacePrefix(program.namespace)))
949
950 phpFile.write(toDefinitions(program.definitions))
951
952 phpFile.write(PHP_TYPES_FOOTER.substitute(source=basename, namespaceSuffix=toPHPNamespaceSuffix(program.namespace)))
953
954 phpFile.close()
955
956def toToImplementationSourceName(filename, genDir=None, debugp=None):
957 """Creates a file name for the public thrift data types based on filename of thrift source file and optional suffix"""
958
959 if not genDir:
960 genDir = toGenDir(filename)
961
962 basename = toBasename(filename)
963
964 result = os.path.join(genDir, basename+".php")
965
966 if debugp:
967 debugp("toToImplementationSourceName("+str(filename)+", "+str(genDir)+") => "+str(basename))
968
969 return result
970
971def writeImplementationSource(program, filename, genDir=None, debugp=None):
972 """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
973thrift source, filename, and the optional generated C++ code directory, genDir, to determine the name and location of header file"""
974
975 toImplementationSource = toToImplementationSourceName(filename, genDir)
976
977 if debugp:
978 debugp("toImplementationSource: "+str(toImplementationSource))
979
980 phpFile = file(toImplementationSource, "w")
981
982 basename = toBasename(filename)
983
984 phpFile.write(PHP_SERVICES_HEADER.substitute(source=basename, date=time.ctime(), namespacePrefix=toPHPNamespacePrefix(program.namespace)))
985
986 # Generate classes for function result "structs"
987
988 privateStructs = []
989
990 for service in program.serviceMap.values():
991
992 phpFile.write(toServiceInterfaceDeclaration(service))
993
994 for function in service.functionList:
995 privateStructs.append(function.resultStruct)
996
997 phpFile.write(toDefinitions(privateStructs))
998
999 serviceGenerator = ClientServiceGenerator()
1000
1001 for service in program.serviceMap.values():
1002
1003 phpFile.write(serviceGenerator(service))
1004
1005 phpFile.write(PHP_SERVICES_FOOTER.substitute(source=basename, namespaceSuffix=toPHPNamespaceSuffix(program.namespace)))
1006
1007 phpFile.close()
1008
1009class PHPGenerator(Generator):
1010
1011 def __call__(self, program, filename, genDir=None, debugp=None):
1012
1013 writeDefinitionHeader(program, filename, genDir, debugp)
1014
1015 writeImplementationSource(program, filename, genDir, debugp)