From 8bcd8efa696b4f7ec4984ca1f971ce2a9cbcc648 Mon Sep 17 00:00:00 2001 From: gggeek Date: Tue, 31 Jan 2023 16:18:56 +0000 Subject: [PATCH] make wrapper and demo code take advantage of get/set options --- demo/server/codegen.php | 25 ++++++++--- src/Wrapper.php | 96 +++++++++++++++++++++++++---------------- 2 files changed, 78 insertions(+), 43 deletions(-) diff --git a/demo/server/codegen.php b/demo/server/codegen.php index 08259266..9ba07fc1 100644 --- a/demo/server/codegen.php +++ b/demo/server/codegen.php @@ -1,13 +1,22 @@ wrapPhpClass( $cm, array( @@ -18,6 +27,10 @@ $code = $w->wrapPhpClass( ) ); +// save the generated code in 3 files: a new class definition, holding all the stub methods, a file with the dispatch-map, +// and a controller, to be accessed from the internet. This split allows to a) hand-edit the controller code if needed, +// and b) later regenerate the stub-methods-holder and dispatch map without touching the controller. +// NB: good security practices dictate that none of those files should be writeable by the webserver user account $targetClassFile = '/tmp/MyServerClass.php'; $targetDispatchMapFile = '/tmp/myServerDispatchMap.php'; $targetControllerFile = '/tmp/myServerController.php'; @@ -33,7 +46,7 @@ file_put_contents($targetClassFile, "class MyServerClass\n{\n\n" ) || die('uh oh'); -// we mangle a bit the code we get from wrapPhpClass to generate a php class instead of a bunch of functions +// we mangle a bit the code we get from wrapPhpClass to turn it into a php class definition instead of a bunch of functions foreach($code as $methodName => $methodDef) { file_put_contents($targetClassFile, ' ' . str_replace(array('function ', "\n"), array('public static function ', "\n "), $methodDef['source']) . "\n\n", FILE_APPEND) || die('uh oh'); @@ -62,15 +75,15 @@ file_put_contents($targetControllerFile, // Wrapper::holdObject('xmlrpc_CommentManager_getComments', $cm); "\$dm = require_once '$targetDispatchMapFile';\n" . - '$s = new \PhpXmlRpc\Server($dm, false);' . "\n" . + '$s = new \PhpXmlRpc\Server($dm, false);' . "\n\n" . '// NB: do not leave these 2 debug lines enabled on publicly accessible servers!' . "\n" . '$s->setOption(\PhpXmlRpc\Server::OPT_DEBUG, 2);' . "\n" . - '$s->setOption(\PhpXmlRpc\Server::OPT_EXCEPTION_HANDLING, 1);' . "\n" . + '$s->setOption(\PhpXmlRpc\Server::OPT_EXCEPTION_HANDLING, 1);' . "\n\n" . '$s->service();' . "\n" ) || die('uh oh'); -// test that everything worked by running it in realtime (note that this will return an xml-rpc error message if run -// from the command line, as the server will find no xml-rpc payload to operate on) +// test that everything worked by running it in realtime (note that this script will return an xml-rpc error message if +// run from the command line, as the server will find no xml-rpc payload to operate on) // *** NB do not do this in prod! The whole concept of code-generation is to do it offline using console scripts/ci/cd *** diff --git a/src/Wrapper.php b/src/Wrapper.php index 4ede26e9..77c638c5 100644 --- a/src/Wrapper.php +++ b/src/Wrapper.php @@ -16,7 +16,6 @@ use PhpXmlRpc\Traits\LoggerAware; * * @todo use some better templating system for 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 add support for 'epivals' mode * @todo allow setting custom namespace for generated wrapping code */ @@ -634,6 +633,9 @@ class Wrapper * - string prefix used for the names of the xml-rpc methods created. * - string replace_class_name use to completely replace the class name with the prefix in the generated method names. e.g. instead of \Some\Namespace\Class.method use prefixmethod * @return array|false false on failure, or on array useable for the dispatch map + * + * @todo allow the generated function to be able to reuse an external Encoder instance instead of creating one on + * each invocation, for the case where all the generated functions will be saved as methods of a class */ public function wrapPhpClass($className, $extraOptions = array()) { @@ -707,26 +709,39 @@ class Wrapper * @param Client $client an xml-rpc client set up correctly to communicate with target server * @param string $methodName the xml-rpc method to be mapped to a php function * @param array $extraOptions array of options that specify conversion details. Valid options include - * - integer signum the index of the method signature to use in mapping (if method exposes many sigs) + * - integer signum the index of the method signature to use in mapping (if + * method exposes many sigs) * - integer timeout timeout (in secs) to be used when executing function/calling remote method * - string protocol 'http' (default), 'http11', 'https', 'h2' or 'h2c' - * - string new_function_name the name of php function to create, when return_source is used. If unspecified, lib will pick an appropriate name - * - string return_source if true return php code w. function definition instead of function itself (closure) - * - bool encode_nulls if true, use `` elements instead of empty string xml-rpc values for php null values - * - bool encode_php_objs let php objects be sent to server using the 'improved' xml-rpc notation, so server can deserialize them as php objects - * - bool decode_php_objs --- WARNING !!! possible security hazard. only use it with trusted servers --- - * - mixed return_on_fault a php value to be returned when the xml-rpc call fails/returns a fault response (by default the Response object is returned in this case). If a string is used, '%faultCode%' and '%faultString%' tokens will be substituted with actual error values - * - bool throw_on_fault if true, throw an exception instead of returning a Response in case of errors/faults; + * - string new_function_name the name of php function to create, when return_source is used. + * If unspecified, lib will pick an appropriate name + * - string return_source if true return php code w. function definition instead of + * the function itself (closure) + * - bool encode_nulls if true, use `` elements instead of empty string xml-rpc + * values for php null values + * - bool encode_php_objs let php objects be sent to server using the 'improved' xml-rpc + * notation, so server can deserialize them as php objects + * - bool decode_php_objs --- WARNING !!! possible security hazard. only use it with + * trusted servers --- + * - mixed return_on_fault a php value to be returned when the xml-rpc call fails/returns + * a fault response (by default the Response object is returned + * in this case). If a string is used, '%faultCode%' and + * '%faultString%' tokens will be substituted with actual error values + * - bool throw_on_fault if true, throw an exception instead of returning a Response + * in case of errors/faults; * if a string, do the same and assume it is the exception class to throw - * - bool debug set it to 1 or 2 to see debug results of querying server for method synopsis - * - int simple_client_copy set it to 1 to have a lightweight copy of the $client object made in the generated code (only used when return_source = true) + * - bool debug set it to 1 or 2 to see debug results of querying server for + * method synopsis + * - int simple_client_copy set it to 1 to have a lightweight copy of the $client object + * made in the generated code (only used when return_source = true) * @return \Closure|string[]|false false on failure, closure by default and array for return_source = true * - * @todo allow the created function to throw exceptions on method calls failures * @todo allow caller to give us the method signature instead of querying for it, or just say 'skip it' * @todo if we can not retrieve method signature, create a php function with varargs * @todo if caller did not specify a specific sig, shall we support all of them? * It might be hard (hence slow) to match based on type and number of arguments... + * @todo when wrapping methods without obj rebuilding, use return_type = 'phpvals' (faster) + * @todo allow creating functions which have an extra `$debug=0` parameter */ public function wrapXmlrpcMethod($client, $methodName, $extraOptions = array()) { @@ -775,9 +790,12 @@ class Wrapper $sigNum = isset($extraOptions['signum']) ? (int)$extraOptions['signum'] : 0; $req = new $reqClass('system.methodSignature'); - $req->addparam(new $valClass($methodName)); + $req->addParam(new $valClass($methodName)); + $origDebug = $client->getOption(Client::OPT_DEBUG); $client->setDebug($debug); + /// @todo move setting of timeout, protocol to outside the send() call $response = $client->send($req, $timeout, $protocol); + $client->setDebug($origDebug); if ($response->faultCode()) { $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': could not retrieve method signature from remote server for method ' . $methodName); return false; @@ -785,7 +803,7 @@ class Wrapper $mSig = $response->value(); /// @todo what about return xml? - if ($client->return_type != 'phpvals') { + if ($client->getOption(Client::OPT_RETURN_TYPE) != 'phpvals') { $decoder = new $decoderClass(); $mSig = $decoder->decode($mSig); } @@ -816,12 +834,15 @@ class Wrapper $mDesc = ''; $req = new $reqClass('system.methodHelp'); - $req->addparam(new $valClass($methodName)); + $req->addParam(new $valClass($methodName)); + $origDebug = $client->getOption(Client::OPT_DEBUG); $client->setDebug($debug); + /// @todo move setting of timeout, protocol to outside the send() call $response = $client->send($req, $timeout, $protocol); + $client->setDebug($origDebug); if (!$response->faultCode()) { $mDesc = $response->value(); - if ($client->return_type != 'phpvals') { + if ($client->getOption(Client::OPT_RETURN_TYPE) != 'phpvals') { $mDesc = $mDesc->scalarVal(); } } @@ -905,7 +926,7 @@ class Wrapper $req = new $reqClass($methodName, $xmlrpcArgs); // use this to get the maximum decoding flexibility - $clientClone->return_type = 'xmlrpcvals'; + $clientClone->setOption(Client::OPT_RETURN_TYPE, 'xmlrpcvals'); $resp = $clientClone->send($req, $timeout, $protocol); if ($resp->faultcode()) { if ($throwFault) { @@ -1011,7 +1032,7 @@ class Wrapper $innerCode .= " \$p$i = \$encoder->encode(\$p$i);\n"; } } - $innerCode .= " \$req->addparam(\$p$i);\n"; + $innerCode .= " \$req->addParam(\$p$i);\n"; $mDesc .= " * @param " . $this->xmlrpc2PhpType($pType) . " \$p$i\n"; } if ($clientCopyMode < 2) { @@ -1029,6 +1050,7 @@ class Wrapper } $mDesc .= "\n */\n"; + /// @todo move setting of timeout, protocol to outside the send() call $innerCode .= " \$res = \${$this_}client->send(\$req, $timeout, '$protocol');\n"; if ($throwFault) { if (!is_string($throwFault)) { @@ -1073,6 +1095,9 @@ class Wrapper * @return string|array|false false on error, the name of the created class if all ok or an array with code, class name and comments (if the appropriate option is set in extra_options) * * @todo add support for anonymous classes in the 'buildIt' case for php > 7 + * @todo add method setDebug() to new class, to enable/disable debugging + * @todo optimization - move the generated Encoder instance to be a property of the created class, instead of creating + * it on every generated method invocation */ public function wrapXmlrpcServer($client, $extraOptions = array()) { @@ -1093,6 +1118,7 @@ class Wrapper // retrieve the list of methods $req = new $reqClass('system.listMethods'); + /// @todo move setting of timeout, protocol to outside the send() call $response = $client->send($req, $timeout, $protocol); if ($response->faultCode()) { $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': could not retrieve method list from remote server'); @@ -1101,7 +1127,7 @@ class Wrapper } $mList = $response->value(); /// @todo what about return_type = xml? - if ($client->return_type != 'phpvals') { + if ($client->getOption(Client::OPT_RETURN_TYPE) != 'phpvals') { $decoder = new $decoderClass(); $mList = $decoder->decode($mList); } @@ -1115,14 +1141,14 @@ class Wrapper if ($newClassName != '') { $xmlrpcClassName = $newClassName; } else { - $xmlrpcClassName = $prefix . '_' . preg_replace(array('/\./', '/[^a-zA-Z0-9_\x7f-\xff]/'), - array('_', ''), $client->server) . '_client'; + /// @todo direct access to $client->server is now deprecated + $xmlrpcClassName = $prefix . '_' . preg_replace(array('/\./', '/[^a-zA-Z0-9_\x7f-\xff]/'), array('_', ''), + $client->server) . '_client'; } while ($buildIt && class_exists($xmlrpcClassName)) { $xmlrpcClassName .= 'x'; } - /// @todo add method setDebug() to new class, to enable/disable debugging $source = "class $xmlrpcClassName\n{\n public \$client;\n\n"; $source .= " function __construct()\n {\n"; $source .= ' ' . str_replace("\n", "\n ", $this->buildClientWrapperCode($client, $verbatimClientCopy, $prefix, static::$namespace)); @@ -1142,7 +1168,7 @@ class Wrapper /// @todo build phpdoc for class definition, too foreach ($mList as $mName) { if ($methodFilter == '' || preg_match($methodFilter, $mName)) { - // note: this will fail if server exposes 2 methods called f.e. do.something and do_something + /// @todo 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->wrapXmlrpcMethod($client, $mName, $opts); @@ -1167,7 +1193,9 @@ class Wrapper if ($allOK) { return $xmlrpcClassName; } else { - $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': could not create class ' . $xmlrpcClassName . ' to wrap remote server ' . $client->server); + /// @todo direct access to $client->server is now deprecated + $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': could not create class ' . $xmlrpcClassName . + ' to wrap remote server ' . $client->server); return false; } } else { @@ -1179,34 +1207,28 @@ class Wrapper * Given necessary info, generate php code that will build a client object just like the given one. * Take care that no full checking of input parameters is done to ensure that valid php code is emitted. * @param Client $client - * @param bool $verbatimClientCopy when true, copy the whole state of the client, except for 'debug' and 'return_type' + * @param bool $verbatimClientCopy when true, copy the whole options of the client, except for 'debug' and 'return_type' * @param string $prefix used for the return_type of the created client * @param string $namespace * @return string */ protected function buildClientWrapperCode($client, $verbatimClientCopy, $prefix = 'xmlrpc', $namespace = '\\PhpXmlRpc\\') { - $code = "\$client = new {$namespace}Client('" . str_replace(array("\\", "'"), array("\\\\", "\'"), $client->path) . - "', '" . str_replace(array("\\", "'"), array("\\\\", "\'"), $client->server) . "', $client->port);\n"; + $code = "\$client = new {$namespace}Client('" . str_replace(array("\\", "'"), array("\\\\", "\'"), $client->getUrl()) . + "');\n"; // copy all client fields to the client that will be generated runtime // (this provides for future expansion or subclassing of client obj) if ($verbatimClientCopy) { - foreach ($client as $fld => $val) { - /// @todo in php 8.0, curl handles became objects, but they have no __set_state, thus var_export will - /// fail for xmlrpc_curl_handle. So we disabled copying it. - /// We should examine in depth if this change can have side effects - at first sight if the - /// client's curl handle is not set, all curl options are (re)set on each http call, so there - /// should be no loss of state... - if ($fld != 'debug' && $fld != 'return_type' && $fld != 'xmlrpc_curl_handle') { + foreach ($client->getOptions() as $opt => $val) { + if ($opt != 'debug' && $opt != 'return_type') { $val = var_export($val, true); - $code .= "\$client->$fld = $val;\n"; + $code .= "\$client->setOption('$opt', $val);\n"; } } } // only make sure that client always returns the correct data type - $code .= "\$client->return_type = '{$prefix}vals';\n"; - //$code .= "\$client->setDebug(\$debug);\n"; + $code .= "\$client->setOption(\PhpXmlRpc\Client::OPT_RETURN_TYPE, '{$prefix}vals');\n"; return $code; } -- 2.47.0