With the support of JSON (which stands for JavaScript Object Notation) and serialized PHP data, what is the need for XML? This question isn't unique to the web services in SugarCRM, as more and more REST-style web services prefer to use JSON over the more structured XML data format because of its lighter weight, easier support for encoding data, and ability to directly consume the payload into rich Ajax applications. JSON, however, isn't the end-all and be-all of data formats for communicating over the web, just as XML wasn't consider the same at its peak either. Even though there are several advantages to using JSON notations over XML, there still are several use-cases where XML is a preferred data format over JSON or serialized PHP:
- If your data set is large, parsing the data all at once can be quite costly. You can process large sets of data more efficiently with an XML pull parser rather than load the entire data structure all at once in memory as you need to do with JSON or serialize PHP.
- XML data can be easily transformed into any display format you like, using XSLT. For example, you can grab the result of the web service call, then do an XSLT transformation to create an HTML document that can be displayed to the user.
- Many legacy applications and programming languages have native support for XML but don't have support for JSON as it's a newer technology. And if you are using anything other than PHP, serialized PHP data handling is also not an option for you.
While many feel that XML is on its way out as a technology, instead it is becoming better able to handle the niche use-cases outlined previously. Support for this technology as an input and output data format is definitely useful to have as a part of the SugarCRM web services.
Customize SugarCRM web services
Begin by adding the custom entry point into the SugarCRM web services API. This entry point is the URL that clients call over HTTP POST to execute SugarCRM web services methods. Because you do not want to alter the existing web services, you instead add a new entry point into the v2 web services (accessed through the entry point service/v2/rest.php) through an entry point defined in the custom directory, which is located at custom/service/v2/rest.php. Listing 1 shows the contents of this entry point.
Listing 1. Web services entry point rest.php
<?php
if(!defined('sugarEntry')) define('sugarEntry', true);
chdir('../../..');
$webservice_class = 'CustomSugarRestService';
$webservice_path = 'custom/service/core/CustomSugarRestService.php';
$webservice_impl_class = 'SugarRestServiceImpl';
$registry_class = 'registry';
$location = '/custom/service/v2/rest.php';
$registry_path = 'service/v2/registry.php';
require_once('service/core/webservice.php');
|
The entry point in Listing 1 specifies variables, which indicate
the various properties you need to set as part of the web service entry point
definition. The key ones to note for this example are the $webservice_class and $webservice_path,
which specify the name of the class and the path, respectively. This class handles the
incoming request made to the webservice and calls the appropriate web services method. It also takes the result from the web services method and packages it appropriately to return the payload to the client. You also specify the path to this entry point in the property $location.
Now let's dissect the webservice class, which is located
at custom/service/core/CustomSugarRestService.php. Listing 2
shows the class definition.
Listing 2.
CustomSugarRestService webservice handler class
<?php
if(!defined('sugarEntry')) define('sugarEntry', true);
require_once('service/core/SugarRestService.php');
class CustomSugarRestService extends SugarRestService
{
/**
* @see SugarRestService::_getTypeName()
*/
protected function _getTypeName($name)
{
if ( strtolower($name) == 'xml' ) {
return 'SugarRestXML';
}
return parent::_getTypeName($name);
}
/**
* @see SugarRestService::__construct()
*/
public function __construct($url)
{
$GLOBALS['log']->info('Begin: SugarRestService->__construct');
$this->restURL = $url;
$this->responseClass = $this->_getTypeName
($_REQUEST['response_type']);
$this->serverClass = $this->_getTypeName($_REQUEST['input_type']);
$GLOBALS['log']->info('SugarRestService->__construct serverclass
= ' . $this->serverClass);
if ( file_exists('service/core/REST/'. $this->serverClass . '.php') ) {
require_once('service/core/REST/'. $this->serverClass . '.php');
}
elseif ( file_exists('custom/service/core/REST/'. $this->serverClass .
'.php') ) {
require_once('custom/service/core/REST/'. $this->serverClass . '.php');
}
else {
$GLOBALS['log']->fatal('ERROR: SugarRestService->__construct serverClass
'.$this->serverClass.' not found');
}
$GLOBALS['log']->info('End: SugarRestService->__construct');
}
/**
* @see SugarRestService::serve()
*/
public function serve()
{
$GLOBALS['log']->info('Begin: SugarRestService->serve');
if ( file_exists('service/core/REST/'. $this->responseClass . '.php') ) {
require_once('service/core/REST/'. $this->responseClass . '.php');
}
elseif ( file_exists('custom/service/core/REST/'. $this->responseClass .
'.php') ) {
require_once('custom/service/core/REST/'. $this->responseClass . '.php');
}
else {
$GLOBALS['log']->fatal('ERROR: SugarRestService->__construct serverClass
'.$this->responseClass.' not found');
}
$response = $this->responseClass;
$responseServer = new $response($this->implementation);
$this->server->faultServer = $responseServer;
$this->responseServer->faultServer = $responseServer;
$responseServer->generateResponse($this->server->serve());
$GLOBALS['log']->info('End: SugarRestService->serve');
}
}
|
In this first part, you first override the SugarRestService::_getTypeName() method, adding the ability to map
the type XML to use the SugarRestXML class that you define in the next section. You also
override the constructor where you instantiate the server class and the SugarRestServiceXML::serve() method to instantiate the response class. These last two changes are needed so the method knows to look in the custom/service/core/REST/ directory for different SugarRest implementations, instead of just in the service/core/REST/ directory.
Now that you have defined the entry point for your custom web service and added the
needed hooks in the webservice class so that you can look
for SugarRest implementations in the custom directory, you can begin to define a class
to handle the XML data being used with your web service. Now define a new class named
SugarRestXML, which is a subclass of the SugarRest class and provides the needed pieces to handle the incoming
XML payload and to return the results back to the client in a proper XML format.
Look at the first part of the equation, where you define the methods that are used to
handle the incoming XML payload. The primary method that handles this task is SugarRestXML::serve(), which is defined in Listing 3.
Listing 3. SugarRestXML.php web services request handling
<?php
if(!defined('sugarEntry')) define('sugarEntry', true);
require_once('service/core/REST/SugarRest.php');
/**
* This class is a XML implementation of REST protocol
*/
class SugarRestXML extends SugarRest
{
/**
* @see SugarRest::serve()
*/
public function serve()
{
$GLOBALS['log']->info('Begin: SugarRestXML->serve');
$xml = !empty($_REQUEST['rest_data'])? $_REQUEST['rest_data']: '';
if(empty($_REQUEST['method']) || !method_exists($this->implementation,
$_REQUEST['method'])){
$er = new SoapError();
$er->set_error('invalid_call');
$this->fault($er);
}
else {
$method = $_REQUEST['method'];
$data = $this->_convertXMLToArray(from_html($xml));
if(!is_array($data))$data = array($data);
$GLOBALS['log']->info('End: SugarRestXML->serve');
return call_user_func_array(array( $this->implementation,
$method),$data);
}
}
|
This method is pretty basic, where it looks for the expected REQUEST variables of method and rest_data being set. The parameter method
specifies the web services method to call, and rest_data contains the XML payload that is passed to the method. The XML format of the incoming request has each parameter being passed to the method as an item under the tag parameters. If any of the parameters accept an array as an input, then it will be "XML-ized" by being tags contained within the parameter's tag. For example, to log in to the SugarCRM instance, you can pass the XML listed in Listing 4 to the login web services method.
Listing 4. XML payload to the login web services method
<?xml version="1.0" encoding="UTF-8"?>
<parameters>
<user_auth>
<user_name>admin</user_name>
<password>{md5 encoded password}</password>
<version>0.1</version>
</user_auth>
<application_name>test</application_name>
<name_value_list />
</parameters>
|
To accept this payload, convert it to a PHP array and pass it to the internal method
that implements the web services method. To do this, you add a simple method, which
leverages the built-in SimpleXML class in PHP. This class simply converts the XML class to a PHP object and then walks through that object and creates an associative array, as in Listing 5.
Listing 5. SugarRestXML.php XML to array conversion
/**
* Converts an XML string to a PHP array
*
* @param string $xml
* @return array
*/
protected function _convertXMLToArray($xml)
{
$returnArray = array();
$xmlObject = simplexml_load_string($xml);
foreach ( $xmlObject->children() as $child ) {
// Attempt to convert the child element to a string; if it comes out a
// non-empty string, then the child element has no children.
$contents = trim((string) $child);
if ( !empty($contents) ) {
$returnArray[$child->getName()] = $contents;
}
else {
// Recursively call this method to convert any
// child arrays to XML
$returnArray[$child->getName()]
= $this->_convertXMLToArray($child->asXML());
}
}
return $returnArray;
}
|
You recursively work through the XML document, using the XML tag name as the key of the
item for the associative array that is built. This array is then passed to the web
services method that you call using the call_user_func_array() function in Listing 3.
Provide XML-formatted XML responses
Now that you've built the way to handle the XML payload of the incoming web service request,
you now build the way to return an XML response to the client. This step involves overriding the SugarRest::generateResponse() and SugarRest::generateFaultResponse() methods, as in Listing 6.
Listing 6. SugarRestXML.php web services response handling
/**
* @see SugarRest::generateResponse()
*/
public function generateResponse($input)
{
// If there is a fault object, return it instead.
if (isset($this->faultObject)) {
$this->generateFaultResponse($this->faultObject);
}
else {
ob_clean();
echo $this->_convertArrayToXML($input);
}
}
/**
* @see SugarRest::generateFaultResponse()
*/
public function generateFaultResponse($errorObject)
{
$error = $errorObject->number . ': ' . $errorObject->name .
'<br>' . $errorObject->description;
$GLOBALS['log']->error($error);
ob_clean();
echo $this->_convertArrayToXML($errorObject);
}
|
SugarRestXML::generateResponse() is called after the web
service method has content to return to the user. It first checks to see whether the
content returned to the user represents a fault object, meaning that it is
returning an error to the user. If it is, then it calls the SugarRestXML::generateFaultResponse() method instead, which logs the
error internally. Either way, the content coming into these methods is either a PHP
array or an object, and it needs to be translated to an XML document instead. You
again do this recursively, as in Listing 7.
Listing 7. SugarRestXML.php array to XML conversion
/**
* Converts a PHP array into XML
*
* @param array $input
* @return string XML
*/
protected function _convertArrayToXML($input)
{
$xmlWriter = new XMLWriter();
$xmlWriter->openMemory();
$xmlWriter->setIndent(true);
$xmlWriter->startDocument('1.0','UTF-8');
$xmlWriter->startElement('result');
foreach ( $input as $key => $value ) {
if ( is_array($value) ) {
$xmlWriter->startElement($key);
$this->_convertArrayItemToXML($value,$xmlWriter);
$xmlWriter->endElement();
}
else {
$xmlWriter->writeElement($key,$value);
}
}
$xmlWriter->endElement();
$xmlWriter->endDocument();
return $xmlWriter->outputMemory();
}
/**
* Converts an item in a PHP array into XML
*
* @param array $item
* @param object XMLWriter $xmlWriter
* @return string XML
*/
protected function _convertArrayItemToXML(array $item, XMLWriter $xmlWriter)
{
foreach ( $item as $key => $value ) {
// If this is an array, we'll call SugarRestXML::_convertArrayItemToXML()
// to convert the array to XML, containing inside the given $key element.
if ( is_array($value) ) {
$xmlWriter->startElement($key);
$this->_convertArrayItemToXML($value,$xmlWriter);
$xmlWriter->endElement();
}
// If it is just a scalar, we can write out the element directly.
else {
$xmlWriter->writeElement($key,$value);
}
}
}
|
Here you leverage the XMLWriter component to build the XML
document to return to the client. You do this over two methods; first call SugarRestXML::_convertArrayToXML() initially to create the XML
declaration header and the root result tags that contain the result from the web
services method call. Then iterate through the result array, calling SugarRestXML::_convertArrayItemToXML() recursively to convert all the
nodes of the result array to a proper XML form. The resulting XML payload is returned
back to the client. Listing 8 shows an example of this payload.
Listing 8. An example of the returned XML payload from the login web services call
<?xml version="1.0" encoding="UTF-8"?> <result> <id>jq4v1mj3a2e0vcer9s2n3pddi4</id> <module_name>Users</module_name> <name_value_list> <user_id> <name>user_id</name> <value>1</value> </user_id> <user_name> <name>user_name</name> <value>admin</value> </user_name> <user_language> <name>user_language</name> <value>en_us</value> </user_language> <user_currency_id> <name>user_currency_id</name> <value></value> </user_currency_id> <user_currency_name> <name>user_currency_name</name> <value>US Dollars</value> </user_currency_name> </name_value_list> </result> |
Just as in the XML payload that you passed to the server, the returned payload has the keys of the associate array returned as tag names in the XML file, with the keys that have arrays as values as children of the specified XML tag. The entire XML payload is returned in the result XML tag.
Call your newly defined web service
Now that you've built the custom web service that can handle and return XML payloads, look at a simple code example of how to do this in Listing 9.
Listing 9. Example script that connects to the custom web service, passing data back and forth in XML format.
<?php
// specify the REST web service to interact with
$url = 'http://localhost/sugarcrm/custom/service/v2/rest.php';
$username = 'username';
$password = md5('password');
// Open a curl session for making the call
$curl = curl_init($url);
// Tell curl to use HTTP POST
curl_setopt($curl, CURLOPT_POST, true);
// Tell curl not to return headers, but do return the response
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
// Set the POST arguments to pass to the Sugar server
$xml = <<<EOXML
<?xml version="1.0" encoding="UTF-8"?>
<parameters>
<user_auth>
<user_name>{$username}</user_name>
<password>{$password}</password>
</user_auth>
</parameters>
EOXML;
$postArgs = array(
'method' => 'login',
'input_type' => 'xml',
'response_type' => 'xml',
'rest_data' => $xml
);
curl_setopt($curl, CURLOPT_POSTFIELDS, $postArgs);
// Make the REST call, returning the result
$response = curl_exec($curl);
// Translate the returned XML data into a SimpleXML object
$xml = simplexml_load_string($response);
// Handle the case of an error response.
if ( !isset($xml->id) ) {
die("Error: {$xml->name} - {$xml->description}\n.");
}
// Echo out successfully returning an session id
echo "Logged in successfully! Session ID is {$xml->id}\n";
|
In this example, you call the login web services method. You
specify both the input_type for the payload you send to the server and response_type
as the payload you expect to have returned as XML, and then you pass the actual XML
data in the rest_data parameter. You then use the PHP
SimpleXML library to parse the XML into a PHP object and grab the id attribute from that class to display the message about a successful login and the returned session id.
In this article, you learned about the continuing value of using XML data structures to interact with web services, namely in the ability to handle large sets of data efficiently and to allow you to transform the returned data into different formats with ease. You then looked at the code to add the ability to use XML payloads with SugarCRM web services by creating a custom entry point. You also looked at both the code to take the incoming XML payload and process it so that the web services methods can use XML data and the code to take the method return array and change it into an XML document to return to the client. Finally, you saw an example script that shows how to call SugarCRM web services using XML data.
| Description | Name | Size | Download method |
|---|---|---|---|
| article source code | sugarcrmxmlrest.source.zip | 6KB | HTTP |
Information about download methods
Learn
- SugarCRM Developer Documentation: Check out the detailed guides to the various SugarCRM APIs.
- The SugarCRM Developer Zone: Explore content for all SugarCRM developers.
- The Definitive Guide to SugarCRM: Better Business Applications: Read an excellent guide to developing application based on SugarCRM.
- Build a RESTful web service (Andrew Glover, developerWorks, July 2008): Learn the fundamental concepts of REST and building applications with Restlets in this step-by-step tutorial.
- Learning
PHP series (Nicholas Chase and Tyler Anderson, developerWorks, June-July 2005): Check out these tutorials on learning to program with PHP.
- PHP.net: Review PHP documentation for the widely-used general-purpose scripting language.
- Recommended PHP reading list: Learn about PHP (Hypertext Preprocessor) with this reading list compiled for programmers and administrators by IBM Web application developers.
- PHP content on developerWorks: Browse Open source library for a wide range of technical articles, tips, tutorials, and other resources.
- PHP project resources: Expand your PHP skills by checking out IBM developerWorks resource.
- XML 1.0
Specification (W3C Recommendation, November 2008): Read this source for specific details about XML features such as the CDATA section.
- The XML FAQ: Visit another excellent source of XML information, edited by Peter Flynn.
- XML DOM tutorial from W3schools.com: Find out what XML-based interfaces are available to the browser (and which browsers support them).
- XHTML™ 1.0: The Extensible HyperText Markup Language (World Wide Web Consortium Recommendation, 26 January 2000): Read more about a family of current and future document types and modules that reproduce, subset, and extend HTML 4.
- XML area on developerWorks: Get the resources you need to advance your skills in the XML arena.
- The developerWorks Open source zone: Find extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products.
- My developerWorks: Personalize your developerWorks experience.
- IBM XML certification: Find out how you can become an IBM-Certified Developer in XML and related technologies.
- XML technical library: See the developerWorks XML Zone library for a wide range of technical articles and tips, tutorials, standards, and IBM Redbooks. Also, read more XML tips.
- developerWorks technical events and webcasts: Stay current with technology in these sessions.
- developerWorks on Twitter: Join today to follow developerWorks tweets.
- developerWorks podcasts: Listen to interesting interviews and discussions for software developers.
- developerWorks on-demand demos: Watch demos ranging from product installation and setup for beginners to advanced functionality for experienced developers.
Get products and technologies
- IBM product evaluation versions: Download or explore the online trials in the IBM SOA Sandbox and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
Discuss
- XML zone discussion forums: Participate in any of several XML-related discussions.
- The developerWorks community: Connect with other developerWorks users while exploring the developer-driven blogs, forums, groups, and wikis.

John Mertic is a software engineer at SugarCRM, and has several years of experience with PHP Web applications. At SugarCRM, he has specialized in data integration, mobile and user interface architecture. An avid writer, he has been published in php|architect, IBM developerWorks, and in the Apple Developer Connector, and is the author of the book 'The Definitive Guide to SugarCRM: Better Business Applications'. He has also contributed to many open source projects, most notably the PHP project where he is the creator and maintainer of the PHP Windows Installer. He can be reached at jmertic@gmail.com.




