xmlrpc_wrappers.inc
来自「PHP 知识管理系统(基于树结构的知识管理系统), 英文原版的PHP源码。」· INC 代码 · 共 818 行 · 第 1/2 页
INC
818 行
<?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 + =
减小字号Ctrl + -
显示快捷键?