Extend SugarCRM REST web services to use XML

Combine XML with SugarCRM to improve how your web services handle large data sets

With the introduction of SugarCRM 5.5, the entire web services framework was invigorated not only to add REST support to complement the existing SOAP support, but also to enable developers to customize the web services in an upgrade-safe way to help support their business applications with ease. Out of the box, the SugarCRM REST web service supports using JSON and serialized PHP data as data formats for interacting with these web services. In this article, you can see how easy it is to add XML as another option to send and receive data with your SugarCRM instance.

Share:

John Mertic, Software Engineer, SugarCRM

Photo of John MerticJohn 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.



26 April 2011

Also available in Chinese Japanese

Why XML?

Frequently used acronyms

  • Ajax: Asynchronous JavaScript + XML
  • API: Application program interface
  • HTML: Hypertext Markup Language
  • HTTP: Hypertext Transfer Protocol
  • REST: Representational State Transfer
  • URL: Uniform Resource Locator
  • W3C: World Wide Web Consortium
  • XML: Extensible Markup Language
  • XSTL: Extensible Stylesheet Language Transformations

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.


Handle XML formatted requests

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.


Conclusion

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.


Download

DescriptionNameSize
article source codesugarcrmxmlrest.source.zip6KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into XML on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML, SOA and web services, Open source
ArticleID=647231
ArticleTitle=Extend SugarCRM REST web services to use XML
publish-date=04262011