--- /dev/null
+<?php
+require_once __DIR__ . "/_prepend.php";
+
+/**
+ * Demoing the code-generation capabilities of the library: create a client class which exposes a bunch of methods
+ * advertised by a remote xml-rpc server.
+ */
+
+use PhpXmlRpc\Client;
+use PhpXmlRpc\Wrapper;
+
+$w = new Wrapper();
+$code = $w->wrapXmlrpcServer(
+ new Client(XMLRPCSERVER),
+ array(
+ 'return_source' => true,
+ 'new_class_name' => 'MyClient',
+ 'method_filter' => '/^examples\./',
+ 'simple_client_copy' => true,
+ )
+);
+
+// the generated code does not have an autoloader included - we need to add in one
+$autoloader = __DIR__ . "/_prepend.php";
+
+$targetFile = '/tmp/MyClient.php';
+$generated = file_put_contents($targetFile,
+ "<?php\n\n" .
+ "require_once '$autoloader';\n\n" .
+ $code['code']
+);
+
+if (!$generated) {
+ die("uh oh");
+}
+
+// *** NB take care when doing this in prod! ***
+// There's a race condition here - what if someone replaces the file we just created, before we include it?
+// You should at the very least make sure that filesystem permissions are tight, so that only the authorized user
+// accounts can write into the folder where $targetFile is created.
+// You might even want to disallow php code executed from the webserver from generating new php code for direct inclusion,
+// and only allow a cli process to do that
+
+include($targetFile);
+
+$client = new MyClient();
+$sorted = $client->examples_sortByAge(array(
+ array('name' => 'Dave', 'age' => 24),
+ array('name' => 'Edd', 'age' => 45),
+ array('name' => 'Joe', 'age' => 37),
+ array('name' => 'Fred', 'age' => 27),
+));
+
+echo "Sorted array:\n";
+print_r($sorted);
--- /dev/null
+<?php
+require_once __DIR__ . "/_prepend.php";
+
+require_once __DIR__.'/methodProviders/CommentManager.php';
+
+use PhpXmlRpc\Server;
+use PhpXmlRpc\Wrapper;
+
+$cm = new CommentManager();
+$w = new Wrapper();
+
+$code = $w->wrapPhpClass(
+ $cm,
+ array(
+ 'method_type' => 'nonstatic',
+ 'return_source' => true,
+ )
+);
+
+// the generated code does not have an autoloader included - we need to add in one
+$autoloader = __DIR__ . "/_prepend.php";
+
+$targetClassFile = '/tmp/MyServerClass.php';
+$targetDispatchMapFile = '/tmp/myServerDM.php';
+
+// generate a file with a class definition
+
+file_put_contents($targetClassFile,
+ "<?php\n\n" .
+ "require_once '$autoloader';\n\n" .
+ "class MyServerClass {\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
+
+foreach($code as $methodName => $methodDef) {
+ file_put_contents($targetClassFile, 'public static ' . $methodDef['source'] . "\n\n", FILE_APPEND) || die('uh oh');
+ $code[$methodName]['function'] = 'MyServerClass::' . $methodDef['function'];
+ unset($code[$methodName]['source']);
+}
+file_put_contents($targetClassFile, "}\n", FILE_APPEND) || die('uh oh');
+
+// and a separate file with the dispatch map
+
+file_put_contents($targetDispatchMapFile,
+ "<?php\n\n" .
+ "require_once '$autoloader';\n\n" .
+ "return " . var_export($code, true) . ";\n"
+) || die('uh oh');
+
+// test that everything worked by running it in realtime
+// *** NB do not do this in prod! The whole concept of code-generation is to do it offline using console scripts/ci/cd ***
+
+include_once $targetClassFile;
+
+// NB: since we are running the generated code within the same script, the existing CommentManager instance will be
+// available for usage by the methods of MyServerClass, as we keep a reference to them within the variable Wrapper::$objHolder
+// but if you are generating a php file for later use, it is up to you to initialize that variables with a
+// CommentManager instance:
+// $cm = new CommentManager();
+// Wrapper::$objHolder['xmlrpc_CommentManager_addComment'] = $cm;
+// Wrapper::$objHolder['xmlrpc_CommentManager_getComments'] = $cm;
+
+$dm = include_once $targetDispatchMapFile;
+$s = new Server($dm, false);
+$s->setDebug(2);
+$s->exception_handling = 1;
+$s->service();
<?php
/**
* A basic comment server. Given an ID it will store a list of names and comment texts against it.
- * It uses a SQLite3 database for storage.
*
* The source code demonstrates:
* - registration of php class methods as xml-rpc method handlers
require_once __DIR__ . "/_prepend.php";
-use PhpXmlRpc\Response;
+require_once __DIR__.'/methodProviders/CommentManager.php';
+
use PhpXmlRpc\Server;
use PhpXmlRpc\Value;
-// NB: this class is totally unaware of the existence of xml-rpc or phpxmlrpc
-class CommentManager
-{
- protected $dbFile = "/tmp/comments.db";
-
- protected function createTable($db)
- {
- return $db->exec('CREATE TABLE IF NOT EXISTS comments (msg_id TEXT NOT NULL, name TEXT NOT NULL, comment TEXT NOT NULL)');
- }
-
- /**
- * NB: we know for a fact that this will be called with 3 string arguments because of the signature used to register
- * this method in the dispatch map. But nothing prevents the client from sending empty strings, nor sql-injection attempts!
- *
- * @param string $msgID
- * @param string $name
- * @param string $comment
- * @return int
- * @throws \Exception
- */
- public function addComment($msgID, $name, $comment)
- {
- $db = new SQLite3($this->dbFile);
- $this->createTable($db);
-
- $statement = $db->prepare("INSERT INTO comments VALUES(:msg_id, :name, :comment)");
- $statement->bindValue(':msg_id', $msgID);
- $statement->bindValue(':name', $name);
- $statement->bindValue(':comment', $comment);
- $statement->execute();
-
- /// @todo this insert-then-count is not really atomic - we should use a transaction
-
- $statement = $db->prepare("SELECT count(*) AS tot FROM comments WHERE msg_id = :id");
- $statement->bindValue(':id', $msgID);
- $results = $statement->execute();
- $row = $results->fetchArray(SQLITE3_ASSOC);
- $results->finalize();
- $count = $row['tot'];
-
- $db->close();
-
- return $count;
- }
-
- /**
- * NB: we know for a fact that this will be called with 1 strin arguments because of the signature used to register
- * this method in the dispatch map. But nothing prevents the client from sending empty strings, nor sql-injection attempts!
- *
- * @param string $msgID
- * @return Response|array[]
- * @throws \Exception
- */
- public function getComments($msgID)
- {
- $db = new SQLite3($this->dbFile);
- $this->createTable($db);
-
- $ra = array();
- $statement = $db->prepare("SELECT name, comment FROM comments WHERE msg_id = :id ORDER BY rowid");
- $statement->bindValue(':id', $msgID);
- $results = $statement->execute();
- while ($row = $results->fetchArray(SQLITE3_ASSOC)) {
- $ra[] = $row;
- }
- $results->finalize();
-
- $db->close();
-
- return $ra;
- }
-}
-
-// Here starts the mapping of CommentManager's methods into xml-rpc methods
-
$manager = new CommentManager();
$addComment_sig = array(array(Value::$xmlrpcInt, Value::$xmlrpcString, Value::$xmlrpcString, Value::$xmlrpcString));
--- /dev/null
+<?php
+
+/**
+ * A basic comment server. Given an ID it will store a list of names and comment texts against it.
+ * It uses a SQLite3 database for storage.
+ * NB: this class is totally unaware of the existence of xml-rpc or phpxmlrpc.
+ */
+class CommentManager
+{
+ protected $dbFile = "/tmp/comments.db";
+
+ protected function createTable($db)
+ {
+ return $db->exec('CREATE TABLE IF NOT EXISTS comments (msg_id TEXT NOT NULL, name TEXT NOT NULL, comment TEXT NOT NULL)');
+ }
+
+ /**
+ * NB: we know for a fact that this will be called with 3 string arguments because of the signature used to register
+ * this method in the dispatch map. But nothing prevents the client from sending empty strings, nor sql-injection attempts!
+ *
+ * @param string $msgID
+ * @param string $name username
+ * @param string $comment comment text
+ * @return int the number of comments for the given message
+ * @throws \Exception
+ */
+ public function addComment($msgID, $name, $comment)
+ {
+ $db = new SQLite3($this->dbFile);
+ $this->createTable($db);
+
+ $statement = $db->prepare("INSERT INTO comments VALUES(:msg_id, :name, :comment)");
+ $statement->bindValue(':msg_id', $msgID);
+ $statement->bindValue(':name', $name);
+ $statement->bindValue(':comment', $comment);
+ $statement->execute();
+
+ /// @todo this insert-then-count is not really atomic - we should use a transaction
+
+ $statement = $db->prepare("SELECT count(*) AS tot FROM comments WHERE msg_id = :id");
+ $statement->bindValue(':id', $msgID);
+ $results = $statement->execute();
+ $row = $results->fetchArray(SQLITE3_ASSOC);
+ $results->finalize();
+ $count = $row['tot'];
+
+ $db->close();
+
+ return $count;
+ }
+
+ /**
+ * NB: we know for a fact that this will be called with 1 string arguments because of the signature used to register
+ * this method in the dispatch map. But nothing prevents the client from sending empty strings, nor sql-injection attempts!
+ *
+ * @param string $msgID
+ * @return array[] each element is a struct, with elements 'name', 'comment'
+ * @throws \Exception
+ */
+ public function getComments($msgID)
+ {
+ $db = new SQLite3($this->dbFile);
+ $this->createTable($db);
+
+ $ra = array();
+ $statement = $db->prepare("SELECT name, comment FROM comments WHERE msg_id = :id ORDER BY rowid");
+ $statement->bindValue(':id', $msgID);
+ $results = $statement->execute();
+ while ($row = $results->fetchArray(SQLITE3_ASSOC)) {
+ $ra[] = $row;
+ }
+ $results->finalize();
+
+ $db->close();
+
+ return $ra;
+ }
+}