add two demos for code generation capabilities
authorgggeek <giunta.gaetano@gmail.com>
Tue, 10 Jan 2023 12:28:29 +0000 (12:28 +0000)
committergggeek <giunta.gaetano@gmail.com>
Tue, 10 Jan 2023 12:28:29 +0000 (12:28 +0000)
demo/client/codegen.php [new file with mode: 0644]
demo/server/codegen.php [new file with mode: 0644]
demo/server/discuss.php
demo/server/methodProviders/CommentManager.php [new file with mode: 0644]

diff --git a/demo/client/codegen.php b/demo/client/codegen.php
new file mode 100644 (file)
index 0000000..2572772
--- /dev/null
@@ -0,0 +1,55 @@
+<?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);
diff --git a/demo/server/codegen.php b/demo/server/codegen.php
new file mode 100644 (file)
index 0000000..103b2e6
--- /dev/null
@@ -0,0 +1,68 @@
+<?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();
index 6f2f905..b31dc3c 100644 (file)
@@ -1,7 +1,6 @@
 <?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));
diff --git a/demo/server/methodProviders/CommentManager.php b/demo/server/methodProviders/CommentManager.php
new file mode 100644 (file)
index 0000000..f517411
--- /dev/null
@@ -0,0 +1,78 @@
+<?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;
+    }
+}