Today, every web developer should know about SOAP (Simple Object Access Protocol). This protocol is used by clients to call server remotely (basically, via HTTP). What SOAP server provides to the outside world – is set of interfaces and methods within these interfaces. Methods may receive arbitrary parameters, these parameters may be arbitrary (like real-world objects, such as UserInfo, etc), but declared in special way.
In other words, SOAP web service may be thought of like “object” with “methods” with specific declarations. There are client and server implementations of SOAP protocol that allow to implement servers and clients with almost any programming language. The main advantage of using SOAP for client-server communication is that service and its methods are described via standardized WSDL document, that may be dynamically downloaded by client and client may generate classes/methods necessary to access the remote service “transparently”.
Good news is that starting from PHP version 5.2.9 (correct me if I am wrong) there is the SOAP PHP extension that allows to implement SOAP client and services using PHP. In this short article I will create a simple web service using PHP language. Why PHP? Well, this scripting language is the most widely-spread across the Internet and is accessible at almost every (even low-cost) web hosting.

Creating web service interface

First of all, I will create methods and declare their parameters that are necessary for my service to accomplish its task. What I will create now is a simple “Chat” service that will provide couple of methods to post a message and to get recently posted messages. In PHP language, this interface may look like this:

// Comes from client to server when user clicks "Send"
class ChatMessage {
	public $from;
	public $to; // Optional
	public $message;
}
 
// List of these objects is by server to client
class ChatEvent {
	public $id;	
	public $from;
	public $to;
	public $text;
}
 
// This object holds list of recent chat events that occurred
// after event with ID=$lastId.
// Instances of this object are returned by Chat service methods.
class ChatEvents {
	public $events = array(); // Array of ChatEvent
	public $lastId;
}
 
// This interface will be provided via SOAP
interface Chat {
	// Returns ChatEvents
	public function Send($params);
 
	// Returns ChatEvents
	public function Poll($params);
}

What should we do next is to create WSDL (Web Services Description Language) document that will describe this interface to the outside world. For this sample I used Eclipse WSDL editor. It is quite easy to make such a simple interface using Eclipse. Here are some screenshots of the WSDL editor:
Eclipse WSDL editor


ChatMessage type

ChatEvent type

Chat events type

And here is the WSDL file content. You may open it in Eclipse (or another WSDL editor) and explore its contents in more detail.

Implementing the service

The above mentioned WSDL and classes are just declarations of interface, its methods and data types used to transfer data to and from the service. What to do next is to implement the web service functionality. What we will need is PHP’s built-in SOAP classes. In order to make it work, it is necessary to enable SOAP standard module in php.ini:

extension=php_soap.dll

Having done that, It is possible to use SOAP stuff in our script. It is quite easy to export web service interface via HTTP:

<?php
$server = new SoapServer("chat.wsdl"); // Path to the service WSDL declaration
$server->setObject(new ChatImpl()); // ChatImpl is the service implementation (see below)
$server->handle();
?>

That’s all! Actually, these 3 lines of code is enough to export methods of the ChatImpl class via HTTP SOAP interface, but the remaining piece of work is to code the ChatImpl itself.

In general, web service declaration (WSDL) is enough to generate proper PHP classes and interfaces, mentioned above, but the built-in PHP SOAP library does not provide neither WSDL generation from PHP classes, nor vice-versa. This means that the only way to create PHP code for the corresponding WSDL declaration is to mirror WSDL declarations by typing all the declared stuff in PHP language (please correct me If I am wrong). In this particular case this is not complicated, so I simply wrote PHP code and WSDL. In Java, C#, etc., producing WSDL from code and vice-versa is matter of single mouse click and done automatically by IDEs and other tools.

Having coded the PHP implementation of the Chat interface I got the following:

<?php
// server.php, SOAP chat sevice
define("CHAT_FILE", "data/chat.txt");
 
// Disable WSDL cache for debugging
ini_set("soap.wsdl_cache_enabled", 0);
 
////////////////////////////////////////////////////
// Public interface declaration (mirrors the WSDL stuff)
 
// Comes from client to server when user clicks "Send"
class ChatMessage {
    public $from;
    public $to; // Optional
    public $message;
}
 
// List of these objects is by server to client
class ChatEvent {
    public $id;
    public $from;
    public $to;
    public $text;
}
 
// This object holds list of recent chat events that occurred
// after event with ID=$lastId.
// Instances of this object are returned by Chat service methods.
class ChatEvents {
    public $events = array(); // Array of ChatEvent
    public $lastId;
}
 
// This interface will be provided via SOAP
interface Chat {
    // Returns ChatEvents
    public function Send($param);
 
    // Returns ChatEvents
    public function Poll($param);
}
 
////////////////////////////////////////////////////
 
// Holds internal server data
class ChatData {
    public $events = array(); // Array of ChatEvent
    public $lastId;
}
 
class ChatImpl implements Chat {
 
    public function Send($param) {
        $messageIn = $param->message; 
        $msg = new ChatEvent();
        $msg->from = $messageIn->from;
        $msg->to = $messageIn->to;
        $msg->text = trim($messageIn->text);
        if(strlen($msg->text) < 1) {
            $msg = null;
        }
        $this->handleChatMessage($msg);
 
        return $this->getEvents($param->lastId); // Return recent messages
    }
 
    public function Poll($param) {
        $this->handleChatMessage(null); // Load messages
        return $this->getEvents($param->lastId); // Return recent messages
    }
 
    private function getEvents($lastId) {
        $filtered = $this->filterEvents($this->chatData->events, $lastId);
        $result = new ChatEvents();
        $result->events = $filtered;
        $result->lastId = $this->chatData->lastId;
        return $result;
    }
 
    private function filterEvents($events, $lastId) {
        $result = array();
        foreach($events as $evt) {
            if($evt->id > $lastId) {
                $result[] = $evt;
            }
        }
        return $result;
    }
 
    private $filePath = CHAT_FILE;
    private $maxEvents = 50;
    private $chatData; // instance of ChatData
 
    private function handleChatMessage($evt) {
        $this->lock();
 
        $data = @file_get_contents($this->filePath);
        $chatData = @unserialize($data);
 
        if(!is_object($chatData)) {
            $chatData = new ChatData();
        }
 
        if(is_object($evt)) {
            $events = $chatData->events;
 
            $nextId = $chatData->lastId + 1; // Assign new ID
            $evt->id = $nextId;
 
            $events[] = $evt; // Append new message to the list
 
            // Ensure that the message list does not grow infinitely
            if(count($events) > $this->maxEvents) {
                $events = array_slice($events, $this->maxEvents - count($events));
            }
 
            // Update chat data...
            $chatData->events = $events;
            $chatData->lastId = $nextId;
 
            // ... and save it back to the data file
            $data = serialize($chatData);
            file_put_contents($this->filePath, $data);
 
            $this->unlock();
        }
        $this->chatData = $chatData;
    }
 
    // Simple locking mechanism
    private $lock = null;
    private function lock() {
        if(!$this->lock) {
            $lock = fopen($this->filePath . ".lock", "w");
            if($lock) {
                flock($lock, LOCK_EX);
            }
            $this->lock = $lock;
        }
    }
 
    private function unlock() {
        fclose($this->lock);
        $this->lock = null;
    }
}
 
$server = new SoapServer("chat.wsdl");
$server->setObject(new ChatImpl());
$server->handle();
// The last PHP cosing tag is omitted intentionally

Using the service

The server.php sample above is complete (but very simple) chat implementation. The final step at server side is to publish the service by uploading it to web hosting.
server.php script needs “data” directory where chat data files will be created. WSDL file should be placed in the same directory as server.php:

Chat files layout

Chat files layout

In order to test the service, one may use Eclipse Web Service Explorer. For the Chat web service, which is uploaded to http://banavoz.com/chat-sample/server.php, WSDL may be obtained via URL: “http://banavoz.com/chatchat-sample/server.php?wsdl”. Pointing Eclipse Web Service Explorer to that URL will reveal service methods and it is possible to call these methods using Eclipse GUI:

Eclipse Web Service Explorer

Eclipse Web Service Explorer


You also may invoke both Send and Post methods of the remote service in GUI:
Invoking Send method via Eclipse GUI

Invoking Send method via Eclipse GUI

As far as you can see, the uploaded Chat web service works fine and now it is possible to create client for the service. Most languages provide appropriate tools to import web service declaration (WSDL) and to generate classes to invoke the service transparently. For example, using Java SDK it is as easy as typing in command line:

wsimport -s . "http://banavoz.com/chat-sample/server.php?wsdl"

This command will load WSDL from the banavoz.com server, parse it and generate Java classes necessary to access the service. “-s .” parameter tells wsimport to generate java source code for all the class files generated. This may be useful for studying internals of the generated client-side code. It is easy now to make a simple Java application that will post a message to chat and receive list of chat events from server:

package chatclient;
 
import java.util.Date;
import java.util.List;
 
import javax.xml.ws.Holder;
 
import com.banavoz.chat.Chat;
import com.banavoz.chat.ChatEvent;
import com.banavoz.chat.ChatMessage;
import com.banavoz.chat.Chat_Service;
 
public class Client {
    public static void main(String[] args) {
        try {
            Chat_Service service = new Chat_Service();
            Chat chat = service.getChatSOAP();
 
            ChatMessage msg = new ChatMessage();
            msg.setFrom("Java client");
            msg.setText("Hello from Java client, date=" + new Date());
 
            // This holder will hold event list from server:
            Holder<List<ChatEvent>> events = new Holder<List<ChatEvent>>();
            // Invoke the service:
            chat.send(msg, new Holder<String>(), events);
 
            for(ChatEvent e: events.value) {
                System.out.println("Message from " + e.getFrom() + ": " + e.getText());
            }
 
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Here is some output of this program:

Message from anonymous: aaaa
Message from anonymous: sss
Message from Java client: Hello from Java client, date=Wed Aug 12 11:45:07 MSD 2009

The remaining thing is to create usable GUI client in Flash or Silverlight to allow users on your site to communicate to each other :) . But this topic is for another article. In fact, creating GUI client is not much about web services. Using web service is just couple of lines of code, so the most of client-side code will be concerned about implementing GUI functionality.

All popular IDEs for Java and other languages (like Eclipse and Visual Studio) allow to import web service and create web service clients using GUI tools. The main idea is that the service exposes standard interface defined in WSDL and any client that supports SOAP protocol may use such service. And PHP now allows web masters to implement different useful services as SOAP web services.

  • Print this article!
  • Digg
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • E-mail this story to a friend!
  • LinkArena
  • LinkedIn
  • MisterWong
  • StumbleUpon
  • Technorati
  • Twitter