namespace PhpXmlRpc;
/**
- * PHP-XMLRPC "wrapper" class.
- * Generate stubs to transparently access xmlrpc methods as php functions and vice-versa.
+ * PHP-XMLRPC "wrapper" class - generate stubs to transparently access xmlrpc methods as php functions and vice-versa.
* Note: this class implements the PROXY pattern, but it is not named so to avoid confusion with http proxies.
*
* @todo use some better templating system for code generation?
*/
class Wrapper
{
- /// used to hold a reference to object instances whose methods get wrapped by wrap_php_function(), in 'create source' mode
+ /// used to hold a reference to object instances whose methods get wrapped by wrapPhpFunction(), in 'create source' mode
public static $objHolder = array();
/**
*
* @return string
*/
- public function php_2_xmlrpc_type($phpType)
+ public function php2XmlrpcType($phpType)
{
switch (strtolower($phpType)) {
case 'string':
case 'integer':
case Value::$xmlrpcInt: // 'int'
case Value::$xmlrpcI4:
+ case Value::$xmlrpcI8:
return Value::$xmlrpcInt;
case Value::$xmlrpcDouble: // 'double'
return Value::$xmlrpcDouble;
*
* @return string
*/
- public function xmlrpc_2_php_type($xmlrpcType)
+ public function xmlrpc2PhpType($xmlrpcType)
{
switch (strtolower($xmlrpcType)) {
case 'base64':
return Value::$xmlrpcString;
case 'int':
case 'i4':
+ case 'i8':
return 'integer';
case 'struct':
case 'array':
* php functions (ie. functions not expecting a single Request obj as parameter)
* is by making use of the functions_parameters_type class member.
*
- * @param string|array $callable the name of the PHP user function to be exposed as xmlrpc method; array($obj, 'methodname') and array('class', 'methodname') are ok too
+ * @param callable $callable the PHP user function to be exposed as xmlrpc method/ a closure, function name, array($obj, 'methodname') or array('class', 'methodname') are ok
* @param string $newFuncName (optional) name for function to be created. Used only when return_source in $extraOptions is true
* @param array $extraOptions (optional) array of options for conversion. valid values include:
* - bool return_source when true, php code w. function definition will be returned, instead of a closure
* @todo what to do when the PHP function returns NULL? We are currently returning an empty string value...
* @todo add an option to suppress php warnings in invocation of user function, similar to server debug level 3?
* @todo add a verbatim_object_copy parameter to allow avoiding usage the same obj instance?
+ * @todo add an option to allow generated function to skip validation of number of parameters, as that is done by the server anyway
*/
- public function wrap_php_function($callable, $newFuncName = '', $extraOptions = array())
+ public function wrapPhpFunction($callable, $newFuncName = '', $extraOptions = array())
{
$buildIt = isset($extraOptions['return_source']) ? !($extraOptions['return_source']) : true;
$plainFuncName = 'Closure';
$exists = true;
- }
- else {
+ } else {
$plainFuncName = $callable;
$exists = function_exists($callable);
}
$funcSigs = $this->buildMethodSignatures($funcDesc);
if ($buildIt) {
- $callable = $this->buildWrapFunctionClosure($callable, $extraOptions, null, null);
+ $callable = $this->buildWrapFunctionClosure($callable, $extraOptions, $plainFuncName, $funcDesc);
} else {
$newFuncName = $this->newFunctionName($callable, $newFuncName, $extraOptions);
$code = $this->buildWrapFunctionSource($callable, $newFuncName, $extraOptions, $plainFuncName, $funcDesc);
}
- /// @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' => $callable,
'signature' => $funcSigs['sigs'],
$desc .= $doc;
} elseif (strpos($doc, '@param') === 0) {
// syntax: @param type $name [desc]
- if (preg_match('/@param\s+(\S+)\s+(\$\S+)\s+(.+)?/', $doc, $matches)) {
+ if (preg_match('/@param\s+(\S+)\s+(\$\S+)\s*(.+)?/', $doc, $matches)) {
$name = strtolower(trim($matches[2]));
//$paramDocs[$name]['name'] = trim($matches[2]);
- $paramDocs[$name]['doc'] = $matches[3];
+ $paramDocs[$name]['doc'] = isset($matches[3]) ? $matches[3] : '';
$paramDocs[$name]['type'] = $matches[1];
}
$i++;
$sigsDocs = array();
foreach ($parsVariations as $pars) {
// build a signature
- $sig = array($this->php_2_xmlrpc_type($funcDesc['returns']));
+ $sig = array($this->php2XmlrpcType($funcDesc['returns']));
$pSig = array($funcDesc['returnsDocs']);
for ($i = 0; $i < count($pars); $i++) {
$name = strtolower($funcDesc['params'][$i]['name']);
if (isset($funcDesc['paramDocs'][$name]['type'])) {
- $sig[] = $this->php_2_xmlrpc_type($funcDesc['paramDocs'][$name]['type']);
+ $sig[] = $this->php2XmlrpcType($funcDesc['paramDocs'][$name]['type']);
} else {
$sig[] = Value::$xmlrpcValue;
}
* @param array $extraOptions
* @param string $plainFuncName
* @param string $funcDesc
- * @return callable
+ * @return \Closure
*/
protected function buildWrapFunctionClosure($callable, $extraOptions, $plainFuncName, $funcDesc)
{
$responseClass = $nameSpace.'Response';
$valueClass = $nameSpace.'Value';
+ // validate number of parameters received
+ // this should be optional really, as we assume the server does the validation
+ $minPars = count($funcDesc['params']);
+ $maxPars = $minPars;
+ foreach ($funcDesc['params'] as $i => $param) {
+ if ($param['isoptional']) {
+ // this particular parameter is optional. We assume later ones are as well
+ $minPars = $i;
+ break;
+ }
+ }
+ $numPars = $req->getNumParams();
+ if ($numPars < $minPars || $numPars > $maxPars) {
+ return new $responseClass(0, 3, 'Incorrect parameters passed to method');
+ }
+
$encoder = new $encoderClass();
$options = array();
if (isset($extraOptions['decode_php_objs']) && $extraOptions['decode_php_objs']) {
* @param array $extraOptions
* @param string $plainFuncName
* @param array $funcDesc
- * @return array
+ * @return string
+ *
+ * @todo add a nice phpdoc block in the generated source
*/
protected function buildWrapFunctionSource($callable, $newFuncName, $extraOptions, $plainFuncName, $funcDesc)
{
$decodePhpObjects = isset($extraOptions['decode_php_objs']) ? (bool)$extraOptions['decode_php_objs'] : false;
$catchWarnings = isset($extraOptions['suppress_warnings']) && $extraOptions['suppress_warnings'] ? '@' : '';
- // build body of new function
-
- $innerCode = "\$encoder = new {$namespace}Encoder();\n";
$i = 0;
$parsVariations = array();
$pars = array();
$pNum = count($funcDesc['params']);
foreach ($funcDesc['params'] as $param) {
- /*$name = strtolower($funcDesc['params'][$i]['name']);
- if (!isset($funcDesc['paramDocs'][$name])) {
- // no param found in phpdoc info matching param definition!
- $funcDesc['paramDocs'][$name]['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 = \$req->getParam($i);\n";
- if ($decodePhpObjects) {
- $innerCode .= "if (\$p{$i}->kindOf() == 'scalar') \$p$i = \$p{$i}->scalarval(); else \$p$i = \$encoder->decode(\$p$i, array('decode_php_objs'));\n";
- } else {
- $innerCode .= "if (\$p{$i}->kindOf() == 'scalar') \$p$i = \$p{$i}->scalarval(); else \$p$i = \$encoder->decode(\$p$i);\n";
- }
- $pars[] = "\$p$i";
+ $pars[] = "\$p[$i]";
$i++;
- if ($param['isoptional']) {
- $innerCode .= "}\n";
- }
if ($i == $pNum) {
// last allowed parameters combination
$parsVariations[] = $pars;
// only known good synopsis = no parameters
$parsVariations[] = array();
$minPars = 0;
+ $maxPars = 0;
} else {
$minPars = count($parsVariations[0]);
+ $maxPars = count($parsVariations[count($parsVariations)-1]);
}
- if ($minPars) {
- // add to code the check for min params number
- // NB: this check needs to be done BEFORE decoding param values
- $innerCode = "\$paramcount = \$req->getNumParams();\n" .
- "if (\$paramcount < $minPars) return new {$namespace}Response(0, " . PhpXmlRpc::$xmlrpcerr['incorrect_params'] . ", '" . PhpXmlRpc::$xmlrpcstr['incorrect_params'] . "');\n" . $innerCode;
+ // build body of new function
+
+ $innerCode = "\$paramCount = \$req->getNumParams();\n";
+ $innerCode .= "if (\$paramCount < $minPars || \$paramCount > $maxPars) return new {$namespace}Response(0, " . PhpXmlRpc::$xmlrpcerr['incorrect_params'] . ", '" . PhpXmlRpc::$xmlrpcstr['incorrect_params'] . "');\n";
+
+ $innerCode .= "\$encoder = new {$namespace}Encoder();\n";
+ if ($decodePhpObjects) {
+ $innerCode .= "\$p = \$encoder->decode(\$req, array('decode_php_objs'));\n";
} else {
- $innerCode = "\$paramcount = \$req->getNumParams();\n" . $innerCode;
+ $innerCode .= "\$p = \$encoder->decode(\$req);\n";
}
- $innerCode .= "\$np = false;\n";
// since we are building source code for later use, if we are given an object instance,
// we go out of our way and store a pointer to it in a static class var var...
if (is_array($callable) && is_object($callable[0])) {
} else {
$realFuncName = $plainFuncName;
}
- foreach ($parsVariations as $pars) {
- $innerCode .= "if (\$paramcount == " . count($pars) . ") \$retval = {$catchWarnings}$realFuncName(" . implode(',', $pars) . "); else\n";
+ foreach ($parsVariations as $i => $pars) {
+ $innerCode .= "if (\$paramCount == " . count($pars) . ") \$retval = {$catchWarnings}$realFuncName(" . implode(',', $pars) . ");\n";
+ if ($i < (count($parsVariations) - 1))
+ $innerCode .= "else\n";
}
- $innerCode .= "\$np = true;\n";
- $innerCode .= "if (\$np) return new {$namespace}Response(0, " . PhpXmlRpc::$xmlrpcerr['incorrect_params'] . ", '" . PhpXmlRpc::$xmlrpcstr['incorrect_params'] . "'); else {\n";
- //$innerCode .= "if (\$_xmlrpcs_error_occurred) return new Response(0, $GLOBALS['xmlrpcerr']user, \$_xmlrpcs_error_occurred); else\n";
$innerCode .= "if (is_a(\$retval, '{$namespace}Response')) return \$retval; else\n";
if ($funcDesc['returns'] == Value::$xmlrpcDateTime || $funcDesc['returns'] == Value::$xmlrpcBase64) {
$innerCode .= "return new {$namespace}Response(new {$namespace}Value(\$retval, '{$funcDesc['returns']}'));";
// if($func->returnsReference())
// return false;
- $code = "function $newFuncName(\$req) {\n" . $innerCode . "}\n}";
+ $code = "function $newFuncName(\$req) {\n" . $innerCode . "\n}";
return $code;
}
* PHP 'wrapper' functions that can be exposed as xmlrpc methods from an xmlrpc server
* object and called from remote clients (as well as their corresponding signature info).
*
- * @param mixed $className the name of the class whose methods are to be exposed as xmlrpc methods, or an object instance of that class
- * @param array $extraOptions see the docs for wrap_php_method for basic options, plus
+ * @param string|object $className the name of the class whose methods are to be exposed as xmlrpc methods, or an object instance of that class
+ * @param array $extraOptions see the docs for wrapPhpMethod for basic options, plus
* - string method_type 'static', 'nonstatic', 'all' and 'auto' (default); the latter will switch between static and non-static depending on whether $className is a class name or object instance
* - string method_filter a regexp used to filter methods to wrap based on their names
* - string prefix used for the names of the xmlrpc methods created
*
* @return array|false false on failure
*/
- public function wrap_php_class($className, $extraOptions = array())
+ public function wrapPhpClass($className, $extraOptions = array())
{
$methodFilter = isset($extraOptions['method_filter']) ? $extraOptions['method_filter'] : '';
$methodType = isset($extraOptions['method_type']) ? $extraOptions['method_type'] : 'auto';
if (($func->isStatic() && ($methodType == 'all' || $methodType == 'static' || ($methodType == 'auto' && is_string($className)))) ||
(!$func->isStatic() && ($methodType == 'all' || $methodType == 'nonstatic' || ($methodType == 'auto' && is_object($className))))
) {
- $methodWrap = $this->wrap_php_function(array($className, $mName), '', $extraOptions);
+ $methodWrap = $this->wrapPhpFunction(array($className, $mName), '', $extraOptions);
if ($methodWrap) {
if (is_object($className)) {
$realClassName = get_class($className);
*
* @return \closure|array|false false on failure, closure by default and array for return_source = true
*/
- public function wrap_xmlrpc_method($client, $methodName, $extraOptions = array())
+ public function wrapXmlrpcMethod($client, $methodName, $extraOptions = array())
{
$newFuncName = isset($extraOptions['new_function_name']) ? $extraOptions['new_function_name'] : '';
* @param string $methodName
* @param array $extraOptions
* @param string $mSig
- * @return callable
+ * @return \Closure
*
* @todo should we allow usage of parameter simple_client_copy to mean 'do not clone' in this case?
*/
$decodeOptions[] = 'decode_php_objs';
}
- /// @todo check for insufficient nr. of args besides excess ones
+ /// @todo check for insufficient nr. of args besides excess ones? note that 'source' version does not...
// support one extra parameter: debug
$maxArgs = count($mSig)-1; // 1st element is the return type
$xmlrpcArgs = array();
foreach($currentArgs as $i => $arg) {
if ($i == $maxArgs) {
- /// @todo log warning? check what happens with the 'source' version
break;
}
$pType = $mSig[$i+1];
- if ($pType == 'i4' || $pType == 'int' || $pType == 'boolean' || $pType == 'double' ||
+ if ($pType == 'i4' || $pType == 'i8' || $pType == 'int' || $pType == 'boolean' || $pType == 'double' ||
$pType == 'string' || $pType == 'dateTime.iso8601' || $pType == 'base64' || $pType == 'null'
) {
// by building directly xmlrpc values when type is known and scalar (instead of encode() calls),
return $function;
}
- protected function buildWrapMethodSource($client, $methodName, array $extraOptions, $newFuncName, $mSig, $mDesc='')
+ /**
+ * @param Client $client
+ * @param string $methodName
+ * @param array $extraOptions
+ * @param string $newFuncName
+ * @param array $mSig
+ * @param string $mDesc
+ * @return array
+ */
+ public function buildWrapMethodSource($client, $methodName, array $extraOptions, $newFuncName, $mSig, $mDesc='')
{
$timeout = isset($extraOptions['timeout']) ? (int)$extraOptions['timeout'] : 0;
$protocol = isset($extraOptions['protocol']) ? $extraOptions['protocol'] : '';
if ($clientCopyMode < 2) {
// client copy mode 0 or 1 == full / partial client copy in emitted code
$verbatimClientCopy = !$clientCopyMode;
- $innerCode = $this->build_client_wrapper_code($client, $verbatimClientCopy, $prefix, $namespace);
+ $innerCode = $this->buildClientWrapperCode($client, $verbatimClientCopy, $prefix, $namespace);
$innerCode .= "\$client->setDebug(\$debug);\n";
$this_ = '';
} else {
for ($i = 1; $i < $pCount; $i++) {
$plist[] = "\$p$i";
$pType = $mSig[$i];
- if ($pType == 'i4' || $pType == 'int' || $pType == 'boolean' || $pType == 'double' ||
+ if ($pType == 'i4' || $pType == 'i8' || $pType == 'int' || $pType == 'boolean' || $pType == 'double' ||
$pType == 'string' || $pType == 'dateTime.iso8601' || $pType == 'base64' || $pType == 'null'
) {
// only build directly xmlrpc values when type is known and scalar
}
}
$innerCode .= "\$req->addparam(\$p$i);\n";
- $mDesc .= '* @param ' . $this->xmlrpc_2_php_type($pType) . " \$p$i\n";
+ $mDesc .= '* @param ' . $this->xmlrpc2PhpType($pType) . " \$p$i\n";
}
if ($clientCopyMode < 2) {
$plist[] = '$debug=0';
$mDesc .= "* @param int \$debug when 1 (or 2) will enable debugging of the underlying {$prefix} call (defaults to 0)\n";
}
$plist = implode(', ', $plist);
- $mDesc .= '* @return ' . $this->xmlrpc_2_php_type($mSig[0]) . " (or an {$namespace}Response obj instance if call fails)\n*/\n";
+ $mDesc .= '* @return ' . $this->xmlrpc2PhpType($mSig[0]) . " (or an {$namespace}Response obj instance if call fails)\n*/\n";
$innerCode .= "\$res = \${$this_}client->send(\$req, $timeout, '$protocol');\n";
if ($decodeFault) {
}
/**
- * Similar to wrap_xmlrpc_method, but will generate a php class that wraps
+ * Similar to wrapXmlrpcMethod, but will generate a php class that wraps
* all xmlrpc methods exposed by the remote server as own methods.
- * For more details see wrap_xmlrpc_method.
+ * For more details see wrapXmlrpcMethod.
*
* For a slimmer alternative, see the code in demo/client/proxy.php
*
- * Note that unlike wrap_xmlrpc_method, we always have to generate php code here. It seems that php 7 will have anon classes...
+ * Note that unlike wrapXmlrpcMethod, we always have to generate php code here. It seems that php 7 will have anon classes...
*
* @param Client $client the client obj all set to query the desired server
- * @param array $extraOptions list of options for wrapped code. See the ones from wrap_xmlrpc_method plus
+ * @param array $extraOptions list of options for wrapped code. See the ones from wrapXmlrpcMethod plus
* - string method_filter regular expression
* - string new_class_name
* - string prefix
*
* @return mixed false on error, the name of the created class if all ok or an array with code, class name and comments (if the appropriatevoption is set in extra_options)
*/
- public function wrap_xmlrpc_server($client, $extraOptions = array())
+ public function wrapXmlrpcServer($client, $extraOptions = array())
{
$methodFilter = isset($extraOptions['method_filter']) ? $extraOptions['method_filter'] : '';
$timeout = isset($extraOptions['timeout']) ? (int)$extraOptions['timeout'] : 0;
/// @todo add function setdebug() to new class, to enable/disable debugging
$source = "class $xmlrpcClassName\n{\npublic \$client;\n\n";
$source .= "function __construct()\n{\n";
- $source .= $this->build_client_wrapper_code($client, $verbatimClientCopy, $prefix, $namespace);
+ $source .= $this->buildClientWrapperCode($client, $verbatimClientCopy, $prefix, $namespace);
$source .= "\$this->client = \$client;\n}\n\n";
$opts = array(
'return_source' => true,
// note: this will fail if server exposes 2 methods called f.e. do.something and do_something
$opts['new_function_name'] = preg_replace(array('/\./', '/[^a-zA-Z0-9_\x7f-\xff]/'),
array('_', ''), $mName);
- $methodWrap = $this->wrap_xmlrpc_method($client, $mName, $opts);
+ $methodWrap = $this->wrapXmlrpcMethod($client, $mName, $opts);
if ($methodWrap) {
if (!$buildIt) {
$source .= $methodWrap['docstring'];
*
* @return string
*/
- protected function build_client_wrapper_code($client, $verbatimClientCopy, $prefix = 'xmlrpc', $namespace = '\\PhpXmlRpc\\' )
+ protected function buildClientWrapperCode($client, $verbatimClientCopy, $prefix = 'xmlrpc', $namespace = '\\PhpXmlRpc\\' )
{
$code = "\$client = new {$namespace}Client('" . str_replace("'", "\'", $client->path) .
"', '" . str_replace("'", "\'", $client->server) . "', $client->port);\n";