📄 xmlrpc_wrappers.php
字号:
<?php/** * PHP-XMLRPC "wrapper" functions * Generate stubs to transparently access xmlrpc methods as php functions and viceversa * * @version $Id: xmlrpc_wrappers.inc,v 1.10 2006/09/01 21:49:19 ggiunta Exp $ * @copyright G. Giunta (C) 2006 * @author Gaetano Giunta * * @todo separate introspection from code generation for func-2-method wrapping * @todo use some better templating system from code generation? * @todo implement method wrapping with preservation of php objs in calls * @todo when wrapping methods without obj rebuilding, use return_type = 'phpvals' (faster) * @todo implement self-parsing of php code for PHP <= 4 */ // requires: xmlrpc.inc /** * Given a string defining a php type or phpxmlrpc type (loosely defined: strings * accepted come from javadoc blocks), return corresponding phpxmlrpc type. * NB: for php 'resource' types returns empty string, since resources cannot be serialized; * for php class names returns 'struct', since php objects can be serialized as xmlrpc structs * @param string $phptype * @return string */ function php_2_xmlrpc_type($phptype) { switch(strtolower($phptype)) { case 'string': return $GLOBALS['xmlrpcString']; case 'integer': case $GLOBALS['xmlrpcInt']: // 'int' case $GLOBALS['xmlrpcI4']: return $GLOBALS['xmlrpcInt']; case 'double': return $GLOBALS['xmlrpcDouble']; case 'boolean': return $GLOBALS['xmlrpcBoolean']; case 'array': return $GLOBALS['xmlrpcArray']; case 'object': return $GLOBALS['xmlrpcStruct']; case $GLOBALS['xmlrpcBase64']: case $GLOBALS['xmlrpcStruct']: return strtolower($phptype); case 'resource': return ''; default: if(class_exists($phptype)) { return $GLOBALS['xmlrpcStruct']; } else { // unknown: might be any 'extended' xmlrpc type return $GLOBALS['xmlrpcValue']; } } } /** * Given a string defining a phpxmlrpc type return corresponding php type. * @param string $xmlrpctype * @return string */ function xmlrpc_2_php_type($xmlrpctype) { switch(strtolower($xmlrpctype)) { case 'base64': case 'datetime.iso8601': case 'string': return $GLOBALS['xmlrpcString']; case 'int': case 'i4': return 'integer'; case 'struct': case 'array': return 'array'; case 'double': return 'float'; case 'undefined': return 'mixed'; case 'boolean': case 'null': default: // unknown: might be any xmlrpc type return strtolower($xmlrpctype); } } /** * Given a user-defined PHP function, create a PHP 'wrapper' function that can * be exposed as xmlrpc method from an xmlrpc_server object and called from remote * clients (as well as its corresponding signature info). * * Since php is a typeless language, to infer types of input and output parameters, * it relies on parsing the javadoc-style comment block associated with the given * function. Usage of xmlrpc native types (such as datetime.dateTime.iso8601 and base64) * in the @param tag is also allowed, if you need the php function to receive/send * data in that particular format (note that base64 encoding/decoding is transparently * carried out by the lib, while datetime vals are passed around as strings) * * Known limitations: * - requires PHP 5.0.3 + * - only works for user-defined functions, not for PHP internal functions * (reflection does not support retrieving number/type of params for those) * - functions returning php objects will generate special xmlrpc responses: * when the xmlrpc decoding of those responses is carried out by this same lib, using * the appropriate param in php_xmlrpc_decode, the php objects will be rebuilt. * In short: php objects can be serialized, too (except for their resource members), * using this function. * Other libs might choke on the very same xml that will be generated in this case * (i.e. it has a nonstandard attribute on struct element tags) * - usage of javadoc @param tags using param names in a different order from the * function prototype is not considered valid (to be fixed?) * * Note that since rel. 2.0RC3 the preferred method to have the server call 'standard' * php functions (ie. functions not expecting a single xmlrpcmsg obj as parameter) * is by making use of the functions_parameters_type class member. * * @param string $funcname the name of the PHP user function to be exposed as xmlrpc method; array($obj, 'methodname') might be ok too, in the future... * @param string $newfuncname (optional) name for function to be created * @param array $extra_options (optional) array of options for conversion. valid values include: * bool return_source when true, php code w. function definition will be returned, not evaluated * bool encode_php_objs let php objects be sent to server using the 'improved' xmlrpc notation, so server can deserialize them as php objects * bool decode_php_objs --- WARNING !!! possible security hazard. only use it with trusted servers --- * bool suppress_warnings remove from produced xml any runtime warnings due to the php function being invoked * @return false on error, or an array containing the name of the new php function, * its signature and docs, to be used in the server dispatch map * * @todo decide how to deal with params passed by ref: bomb out or allow? * @todo finish using javadoc info to build method sig if all params are named but out of order * @todo add a check for params of 'resource' type * @todo add some trigger_errors / error_log when returning false? * @todo what to do when the PHP function returns NULL? we are currently an empty string value... * @todo add an option to suppress php warnings in invocation of user function, similar to server debug level 3? */ function wrap_php_function($funcname, $newfuncname='', $extra_options=array()) { $buildit = isset($extra_options['return_source']) ? !($extra_options['return_source']) : true; $prefix = isset($extra_options['prefix']) ? $extra_options['prefix'] : 'xmlrpc'; $encode_php_objects = isset($extra_options['encode_php_objs']) ? (bool)$extra_options['encode_php_objs'] : false; $decode_php_objects = isset($extra_options['decode_php_objs']) ? (bool)$extra_options['decode_php_objs'] : false; $catch_warnings = isset($extra_options['suppress_warnings']) && $extra_options['suppress_warnings'] ? '@' : ''; if(version_compare(phpversion(), '5.0.3') == -1) { // up to php 5.0.3 some useful reflection methods were missing error_log('XML-RPC: cannot not wrap php functions unless running php version bigger than 5.0.3'); return false; } if((is_array($funcname) && !method_exists($funcname[0], $funcname[1])) || !function_exists($funcname)) { error_log('XML-RPC: function to be wrapped is not defined: '.$funcname); return false; } else { // determine name of new php function if($newfuncname == '') { if(is_array($funcname)) { $xmlrpcfuncname = "{$prefix}_".implode('_', $funcname); } else { $xmlrpcfuncname = "{$prefix}_$funcname"; } } else { $xmlrpcfuncname = $newfuncname; } while($buildit && function_exists($xmlrpcfuncname)) { $xmlrpcfuncname .= 'x'; } // start to introspect PHP code $func =& new ReflectionFunction($funcname); if($func->isInternal()) { // Note: from PHP 5.1.0 onward, we will possibly be able to use invokeargs // instead of getparameters to fully reflect internal php functions ? error_log('XML-RPC: function to be wrapped is internal: '.$funcname); return false; } // retrieve parameter names, types and description from javadoc comments // function description $desc = ''; // type of return val: by default 'any' $returns = $GLOBALS['xmlrpcValue']; // desc of return val $returnsDocs = ''; // type + name of function parameters $paramDocs = array(); $docs = $func->getDocComment(); if($docs != '') { $docs = explode("\n", $docs); $i = 0; foreach($docs as $doc) { $doc = trim($doc, " \r\t/*"); if(strlen($doc) && strpos($doc, '@') !== 0 && !$i) { if($desc) { $desc .= "\n"; } $desc .= $doc; } elseif(strpos($doc, '@param') === 0) { // syntax: @param type [$name] desc if(preg_match('/@param\s+(\S+)(\s+\$\S+)?\s+(.+)/', $doc, $matches)) { if(strpos($matches[1], '|')) { //$paramDocs[$i]['type'] = explode('|', $matches[1]); $paramDocs[$i]['type'] = 'mixed'; } else { $paramDocs[$i]['type'] = $matches[1]; } $paramDocs[$i]['name'] = trim($matches[2]); $paramDocs[$i]['doc'] = $matches[3]; } $i++; } elseif(strpos($doc, '@return') === 0) { // syntax: @return type desc //$returns = preg_split('/\s+/', $doc); if(preg_match('/@return\s+(\S+)\s+(.+)/', $doc, $matches)) { $returns = php_2_xmlrpc_type($matches[1]); if(isset($matches[2])) { $returnsDocs = $matches[2]; } } } } } // execute introspection of actual function prototype $params = array(); $i = 0; foreach($func->getParameters() as $paramobj) { $params[$i] = array(); $params[$i]['name'] = '$'.$paramobj->getName(); $params[$i]['isoptional'] = $paramobj->isOptional(); $i++; } // start building of PHP code to be eval'd $innercode = ''; $i = 0; $parsvariations = array(); $pars = array(); $pnum = count($params); foreach($params as $param) { if (isset($paramDocs[$i]['name']) && $paramDocs[$i]['name'] && strtolower($paramDocs[$i]['name']) != strtolower($param['name'])) { // param name from phpdoc info does not match param definition! $paramDocs[$i]['type'] = 'mixed'; } if($param['isoptional']) { // this particular parameter is optional. save as valid previous list of parameters $innercode .= "if (\$paramcount > $i) {\n"; $parsvariations[] = $pars; } $innercode .= "\$p$i = \$msg->getParam($i);\n"; if ($decode_php_objects) { $innercode .= "if (\$p{$i}->kindOf() == 'scalar') \$p$i = \$p{$i}->scalarval(); else \$p$i = php_{$prefix}_decode(\$p$i, array('decode_php_objs'));\n"; } else { $innercode .= "if (\$p{$i}->kindOf() == 'scalar') \$p$i = \$p{$i}->scalarval(); else \$p$i = php_{$prefix}_decode(\$p$i);\n"; } $pars[] = "\$p$i"; $i++; if($param['isoptional']) { $innercode .= "}\n"; } if($i == $pnum) { // last allowed parameters combination $parsvariations[] = $pars; } } $sigs = array(); $psigs = array(); if(count($parsvariations) == 0) { // only known good synopsis = no parameters $parsvariations[] = array(); $minpars = 0; } else { $minpars = count($parsvariations[0]); } if($minpars) { // add to code the check for min params number // NB: this check needs to be done BEFORE decoding param values $innercode = "\$paramcount = \$msg->getNumParams();\n" . "if (\$paramcount < $minpars) return new {$prefix}resp(0, {$GLOBALS['xmlrpcerr']['incorrect_params']}, '{$GLOBALS['xmlrpcstr']['incorrect_params']}');\n" . $innercode; } else { $innercode = "\$paramcount = \$msg->getNumParams();\n" . $innercode; } $innercode .= "\$np = false;\n"; foreach($parsvariations as $pars) { $innercode .= "if (\$paramcount == " . count($pars) . ") \$retval = {$catch_warnings}$funcname(" . implode(',', $pars) . "); else\n"; // build a 'generic' signature (only use an appropriate return type) $sig = array($returns); $psig = array($returnsDocs); for($i=0; $i < count($pars); $i++) { if (isset($paramDocs[$i]['type'])) { $sig[] = php_2_xmlrpc_type($paramDocs[$i]['type']); } else { $sig[] = $GLOBALS['xmlrpcValue']; } $psig[] = isset($paramDocs[$i]['doc']) ? $paramDocs[$i]['doc'] : ''; } $sigs[] = $sig; $psigs[] = $psig; } $innercode .= "\$np = true;\n"; $innercode .= "if (\$np) return new {$prefix}resp(0, {$GLOBALS['xmlrpcerr']['incorrect_params']}, '{$GLOBALS['xmlrpcstr']['incorrect_params']}'); else {\n"; //$innercode .= "if (\$_xmlrpcs_error_occurred) return new xmlrpcresp(0, $GLOBALS['xmlrpcerr']user, \$_xmlrpcs_error_occurred); else\n"; $innercode .= "if (is_a(\$retval, '{$prefix}resp')) return \$retval; else\n"; if($returns == $GLOBALS['xmlrpcDateTime'] || $returns == $GLOBALS['xmlrpcBase64']) { $innercode .= "return new {$prefix}resp(new {$prefix}val(\$retval, '$returns'));"; } else { if ($encode_php_objects) $innercode .= "return new {$prefix}resp(php_{$prefix}_encode(\$retval, array('encode_php_objs')));\n"; else $innercode .= "return new {$prefix}resp(php_{$prefix}_encode(\$retval));\n"; } // shall we exclude functions returning by ref? // if($func->returnsReference()) // return false; $code = "function $xmlrpcfuncname(\$msg) {\n" . $innercode . "}\n}"; //print_r($code); if ($buildit) { $allOK = 0; eval($code.'$allOK=1;'); // alternative //$xmlrpcfuncname = create_function('$m', $innercode); if(!$allOK) { error_log('XML-RPC: could not create function '.$xmlrpcfuncname.' to wrap php function '.$funcname); return false; } } /// @todo examine if $paramDocs matches $parsvariations and build array for /// usage as method signature, plus put together a nice string for docs $ret = array('function' => $xmlrpcfuncname, 'signature' => $sigs, 'docstring' => $desc, 'signature_docs' => $psigs, 'source' => $code); return $ret; } } /** * Given an xmlrpc client and a method name, register a php wrapper function * that will call it and return results using native php types for both * params and results. The generated php function will return an xmlrpcresp * oject for failed xmlrpc calls * * Known limitations: * - server must support system.methodsignature for the wanted xmlrpc method * - for methods that expose many signatures, only one can be picked (we * could in priciple check if signatures differ only by number of params * and not by type, but it would be more complication than we can spare time)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -