Backend Development

Learn About XML RPC (Remote Procedure Calls) In PHP

Learn About XML RPC (Remote Procedure Calls) In PHP

In this article we will learn about XMLRPC transport protocol in PHP and how to install the XMLRPC pecl package and later we will use a composer package instead.

 

 

 

Overview

XMLRPC (Remote Procedure Calls) is a technique used in previous days of the internet to allow software especially web software to interchange data and communicate with each other using HTTP and XML. In this time many systems used XMLRPC till now, like wordpress and most blogging platforms. So to publish content in a blogging platform was to use a third party desktop application that uploads the data using XMLRPC. However this technique is not so secure and can lead to many vulnerabilities.

After the emergence of REST and JSON, applications have become using these techniques instead of XMLRPC because it provides more security and ease of use than the XMLRPC, even most platforms now depends on REST API’s.

 

XMLRPC Components

There are two main components when implementing the XMLRPC:

  • Server: Initialize the XMLRPC server and contains the operations or API’s that the client will request. These API’s can be implemented in PHP using pure functions or a class that contains these operations as public methods.
  • Client: Represent the bootstrap file that access any operation in the server by using a URI segment or query string and return the response as xml.

You can see in this illustration a sample client-server of a simple blog that contains three operations (getPosts, findPost, deletePost).

XML RPC - components

 

XMLRPC XML Structure

As the response of the XMLRPC is XML, the application that accesses these data should be able to parse this xml structure to be display in some kind of GUI. For this reason any programming language or technology that produces XMLRPC should have standard structure similar to this:

<?xml version="1.0" encoding="iso-8859-1"?>
<methodResponse>
   <params>
       <param>
           <value>
              <array>
                  <data>
                     <value>
                         <struct>
                             <member>
                                  <name>id</name>
                                  <value>
                                     <int>1</int>
                                  </value>
                             </member>
                             <member>
                                  <name>title</name>
                                  <value>
                                     <string>Hello World</string>
                                  </value>
                             </member>
                         </struct>
                     </value>
                     <value>
                     </value>
                  </data>
              </array>
           </value>
       </param>
   </params>
</methodResponse>

In this XML the entire xml is wrapped in these tags <methodResponse><params><param><value><array><data> tags. Usually the response will be an array of items like array of posts, so inside the <data> tag each item is wrapped inside <value><struct> tags.

The <member> tag represent each field in the item, for the posts example the member may represent id, title, createdDate.

Each member have name inside the <name> tag and value inside the <value> tag. Finally the actual value should be wrapped inside another tag that identifies the type of data like <int> for integers, <string> for strings and so on.

 

Implementing XMLRPC

To implement XMLRPC in PHP there is PHP extension, but unfortunately this extension is still experimental (don’t use it in production), you can check the docs in this link. For this reason there are alternative libraries which we will use once of them below.

To install the and test the xmlrpc PHP extension in ubuntu linux for PHP 8.0:

sudo apt-get install php8.0-xmlrpc

Or using pecl command:

pecl install channel://pecl.php.net/xmlrpc-1.0.0RC3

I have made a simple example using this extension which you can download from this link.

You can test the xmlrpc server in postman like shown in these figures

xmlrpc testing in postman 1

 

xmlrpc testing in postman 2

 

 

Let’s see another package written in PHP to make XMLRPC requests similar to the PHP extension above. This is a composer package laminas/laminas-xmlrpc you can find it here.

Install this package using composer:

$ composer require laminas/laminas-xmlrpc

Creating Server and client using this package is much easy.

To create an XmlRpc server:

$server = new Laminas\XmlRpc\Server();
$server->setClass('My\Service\Class');
echo $server->handle();

Here we created new instance of Laminas\XmlRpc\Server class. Then using Server::setClass() method we can pass a class name which contains the operations the client will expose. Finally calling Server::handle() method to process the request.

Similarly to create an XmlRpc client:

$client = new Laminas\XmlRpc\Client('http://server url');

var_dump($client->call('test.guess', [$arg1, $arg2]));

we created new instance of Laminas\XmlRpc\Client class. the constructor accepts the url of the XmlRpc server. After that using client->call() method we can call a server method using the name as the first parameter and an array of arguments as the second parameter if it accepts parameters.

 

To better understand how to use this package we will create a simple real world example.

Create a Mysql database name it as you want and then create just one table, the structure of this table as follows:

CREATE TABLE `posts` (
 `id` int NOT NULL AUTO_INCREMENT,
 `title` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
 `description` text COLLATE utf8mb4_general_ci NOT NULL,
 `date_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

Then insert this sample data using the insert statement:

insert into posts (title, description) values ('What is Lorem Ipsum?', 'Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book'), ('Why do we use it?', 't is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using Content here, content here'), ( 'Where does it come from?
', 'Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia')

Create new folder project named “xmlrpc-tutorial” with this structure:xmlrpc tutorial - project structure

 

 

 

Inside the project directory open the terminal and initialize composer project with:

composer init

Complete all the steps that will be displayed in the terminal. Then hit this command to install the Laminas xmlrpc package:

$ composer require laminas/laminas-xmlrpc

 

After that open each file and update as follows:

In the db_config.php add the db settings like so:

<?php
$servername = "localhost";
$dbname = "xmlrpc_tutorial";
$username = "<db name>";
$password = "<db password>";

Open Post.php and create this class:

<?php

class Post
{
    private $connection;

    public function __construct()
    {
        global $servername, $username, $password, $dbname;

        try {
            $this->connection = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
            $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        } catch(PDOException $e) {
            die("Connection failed: " . $e->getMessage());
        }
    }

    /**
     * Get array of posts
     *
     * @return array
     */
    public function getPosts() : array
    {
        $query = $this->connection->prepare("SELECT * FROM posts");
        $query->execute();

        $result = $query->fetchAll();

        return $result;
    }

    /**
     * Find single post
     *
     * @param int $id
     * @return mixed
     */
    public function find($id) : mixed
    {
        $sql = "SELECT * FROM posts where id=$id";

        $query = $this->connection->prepare($sql);
        $query->execute();

        $row = $query->fetch();

         return $row;
    }

    /**
     * Create new post
     *
     * @param array $payload
     * @return mixed
     */
    public function create($payload) : mixed
    {
        [$title, $description] = $payload;

        $sql = "INSERT INTO posts (title, description) VALUES (?,?)";
        $stmt= $this->connection->prepare($sql);
        $stmt->execute([$title, $description]);
        $inserted = $this->connection->lastInsertId();

        return $this->find($inserted);
    }

    /**
     * Update post
     *
     * @param array $payload
     * @param int $id
     * @return mixed
     */
    public function update($payload, $id) : mixed
    {
        [$title, $description] = $payload;

        $sql = "UPDATE posts SET title=?, description=? WHERE id=?";
        $stmt= $this->connection->prepare($sql);
        $stmt->execute([$title, $description, $id]);

        return $this->find($id);
    }

    /**
     * Delete post
     *
     * @param int $id
     * @return bool
     */
    public function delete($id) : bool
    {
        $sql = "DELETE FROM posts WHERE id=?";
        $stmt= $this->connection->prepare($sql);
        $stmt->execute([$id]);

        return true;
    }

    public function __destruct()
    {
        $this->connection = null;
    }
}

This is just a sample class to manipulate post CRUD like fetching all posts, fetching single post, creating and so on represented as public methods like getPosts(), find(), create(), update(), delete(). In real world application the DB handler will be in a separate service, but here is just for demonstrating the example.

The docblock above each operation is very very important and is not optional, in order for Laminas package to identify the method signature and the number of parameters and type. For example the find($id) method accepts an integer parameter $id, but the Laminas package cannot identify this automatically but it reads the docblock for this operation determine the params from the @param annotation.

 

The next thing open server.php and update as below:

include_once '../vendor/autoload.php';
include_once './db_config.php';
include_once './Services/Post.php';

$server = new Laminas\XmlRpc\Server();
$server->sendArgumentsToAllMethods(false);
$server->setClass(Post::class, 'post');    // access any method in client as post.*
echo $server->handle();

In this code at the top i am including the autoload.php file from the composer vendor/ directory, then i included db_config.php and the our service class Post.php. Next i initialized new Laminas\XmlRpc\Server instance.

I registered the Post class using Server::setClass() method passing in the class name and optional namespace. Don’t be confused with the namespace parameter and the class namespace. The namespace parameter used to categorize the services in case you have multiple services. For example we might have several services like Post, Comment, Product, etc.

when providing a namespace the client can access any method using notation “namespace.method” like if we need to to access the find() method above we can do so using “post.find“.

Finally i called the Server::handle() to process the request.

The last piece to update is the client.php:

<?php
include_once '../vendor/autoload.php';

$client = new Laminas\XmlRpc\Client('http://localhost/xmlrpc-tutorial/server.php');

$params = $_REQUEST['params'] ?? [];

$args = array_values($params);

try {
    $result = $client->call($_REQUEST['operation'], $args);
    echo $client->getLastResponse();   // return xml response
} catch (Laminas\XmlRpc\Client\Exception\FaultException $e) {
    echo $e->getCode();
    echo $e->getMessage();
}

In this code i created new instance of Laminas\XmlRpc\Client, passing in the server url in the constructor. Using the client instance i invoked the call() method which accepts the method and params respectively.

To be more dynamic i received the method and params from request. To retrieve the response as xml i invoked getLastResponse() method.

Here are some screenshots to show the usage in postman

xmlrpc - postman4 xmlrpc - postman3 xmlrpc - postman2 xmlrpc - postman1

 

1 1 vote
Article Rating

What's your reaction?

Excited
0
Happy
0
Not Sure
0
Confused
0

You may also like

Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments