扩展 SugarCRM REST web 服务以使用 XML

结合 XML 和 SugarCRM 来改进您的 Web 服务处理大型数据集的方式

随着 SugarCRM 5.5 的引入,整个 web 服务框架倍添活力,不仅添加了 REST 支持来补充现有 SOAP 支持,而且能够让开发人员以一种安全升级的方式定制 web 服务,以轻松帮助支持其业务应用。开箱即用的 SugarCRM REST web 服务支持将 JSON 和序列化 PHP 数据作为数据格式与这些 web 服务交互。在本文中,您可以看到使用 SugarCRM 实例收发 XML 格式的数据有多么简单。

John Mertic, 软件工程师, SugarCRM

John Mertic 的照片John Mertic 是 SugarCRM 公司的一位软件工程师,拥有数年 PHP Web 应用程序经验。在 SugarCRM,他专攻数据集成、移动和用户界面架构。作为一位热心的作者,他已在 php|architect、IBM developerWorks 和 Apple Developer Connector 中发表了多篇文章,他还是 “The Definitive Guide to SugarCRM: Better Business Applications” 一书的作者。他曾经为很多开源项目做出过贡献,最著名的是 PHP 项目;在该项目中,他是 PHP Windows Installer 的创建者和维护者。



2011 年 6 月 07 日

为何使用 XML?

常用缩略词

  • Ajax:异步 JavaScript + XML
  • API:应用程序编程接口
  • HTML:超文本标记语言
  • HTTP:超文本传输协议
  • REST:具象状态传输
  • URL:统一资源定位符
  • W3C:万维网联盟
  • XML:可扩展标记语言
  • XSTL:可扩展样式表语言转换

既然有 JSON(代表 JavaScript Object Notation)和序列化 PHP 数据的支持,为何还需要 XML?这个问题不是 SugarCRM 中的 web 服务所特有的,因为有越来越多的 REST 式 web 服务选择使用 JSON 而胜过更结构化的 XML 数据格式,因为 JSON 具有对编码数据的轻量级简易支持,且能够直接将有效负载应用到丰富的 Ajax 应用程序中。但是 JSON 并非 web 通信中使用的所有数据格式的终结版,就像 XML 在其顶峰时期没有被看作终结版一样。尽管使用 JSON 比使用 XML 具有很多优势,但在有些情况下 XML 还是胜过 JSON 或序列化 PHP 的首选数据格式:

  • 如果您的数据集很大,一次性解析所有数据会很昂贵。您可以使用一个 XML pull parser 更有效地处理大型数据集,而非一次性地将整个数据结构加载到内存中,就像使用 JSON 或序列化 PHP 那样。
  • 使用 XSLT 可将 XML 数据轻松转换成您喜欢的任何显示格式。例如,您可以抓取 web 服务调用的结果,然后执行一个 XSLT 转换来创建一个可显示给用户的 HTML 文档。
  • 许多遗留应用程序和编程语言具有对 XML 的原生支持,但是不支持 JSON,因为 JSON 是一个较新的技术。如果您在使用 PHP 之外的语言,那么序列化 PHP 数据处理也不适合您。

虽然很多人认为 XML 作为一种技术的地位已经开始动摇,相反,它变得更善于处理之前提到的用例。支持将该技术作为输入和输出数据格式对于 SugarCRM web 服务绝对是很有益的。


定制 SugarCRM web 服务

首先添加自定义切入点到 SugarCRM web 服务 API 中。该切入点是客户端通过 HTTP POST 调用的 URL,以执行 SugarCRM web 服务方法。如果您不想修改现有的 web 服务,那么可以添加一个新的切入点到 v2 web 服务(通过切入点 service/v2/rest.php 访问),使用自定义目录中定义的一个切入点,位于 custom/service/v2/rest.php。清单 1 显示该切入点的内容。

清单 1. Web 服务切入点 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');

清单 1 中的切入点指定变量,表示定义 web 服务切入点需要设置的各种属性。本例中需要注意的关键内容是 $webservice_class$webservice_path,它们分别指定类和路径的名称。该类处理对 webservice 做出的传入请求,并调用合适的 web 服务方法。它还接受来自 web 服务的结果,并对其适当地打包,以将有效负载返回给客户端。您还可以在 $location 属性中指定该切入点的路径。

现在我们来解析 webservice 类,它位于 custom/service/core/CustomSugarRestService.php。清单 2 显示类定义。

清单 2. CustomSugarRestService webservice 处理程序类
<?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');
        }
}

在第一部分,我们首先重写了 SugarRestService::_getTypeName() 方法,添加映射 XML 类型的功能,以使用 SugarRestXML 类,该类会在下一节中定义。我们还重写了构造函数,其中实例化了服务器类和 SugarRestServiceXML::serve() 方法,以实例化响应类。最后这两个更改是必需的,这样方法才知道要查看 custom/service/core/REST/ directory 寻找不同的 SugarRest 实现,而非仅仅查看 service/core/REST/directory。


处理 XML 格式的请求

现在已经定义了您的自定义 web 服务的切入点,并在 webservice 类中添加了所需的 hooks,这样您就可以在自定义目录中查找 SugarRest 实现,可以开始定义一个类来处理 web 服务中使用的 XML 数据。现在定义一个名为 SugarRestXML 的新类,该类是 SugarRest 类的一个子类,提供处理传入的 XML 有效负载所需的部分,并以适当的 XML 格式将结果返回给客户端。

我们来看程式的第一部分,其中定义了用于处理传入的 XML 有效负载的方法。处理该任务的主要方法是 SugarRestXML::serve(),这在 清单 3 中有定义。

清单 3. SugarRestXML.php web 服务请求处理
<?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);
               }
        }

这种方法是非常基本的,它寻找正在设置中的 methodrest_data 的预期 REQUEST 变量。method 参数指定要调用的 web 服务方法,且 rest_data 包含传递给该方法的 XML 有效负载。传入请求的 XML 格式将每个参数作为 parameters 标记下的每个项目传递给方法。如果任何参数将数组作为输入接受,那么它会被 XML 化,作为包含在参数的标记中的标记。例如,要登录到一个 SugarCRM 实例,您可以将 清单 4 中列出的 XML 传递给 login web 服务方法。

清单 4. 登录 web 服务方法的 XML 有效负载
<?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>

要接受该有效负载,将其转换为一个 PHP 数组,并将其传递给实现 web 服务方法的内部方法。为此,您可以添加一个简单的方法,该方法利用 PHP 中的内置 SimpleXML 类。该类仅仅将 XML 类转化为一个 PHP 对象,然后遍历该对象,并创建一个关联数组,如 清单 5 所示。

清单 5. 进行数组转换的 SugarRestXML.php XML
/**
         * 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;
        }

递归遍历 XML 文档,使用 XML 标记名作为所构建的关联数组的项目的键。然后该数组被传递给 web 服务方法,您可以使用 清单 3 中的 call_user_func_array() 函数调用该方法。


提供 XML 格式的 XML 响应

您已经构建了处理传入 web 服务请求的 XML 有效负载的方式,现在可以构建将 XML 响应返回给客户端的方式了。这一步骤涉及到重写 SugarRest::generateResponse()SugarRest::generateFaultResponse() 方法,如 清单 6 所示。

清单 6. SugarRestXML.php web 服务响应处理
   /**
         * @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);
        }

在 web 服务方法同意返回给用户之后,SugarRestXML::generateResponse() 被调用。它首先检查看返回给用户的内容代表一个错误对象,也就是说它返回一个错误给用户。如果是,那么它调用 SugarRestXML::generateFaultResponse() 方法,该方法在内部记录错误。总之,传入这些方法的内容要么是一个 PHP 数组,要么是一个对象,需要将其转换成一个 XML 文档。同样递归地进行该操作,如 清单 7 所示。

清单 7. 进行 XML 转换的 SugarRestXML.php 数组
           /**
         * 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);
                }
       }
        }

这里我们利用 XMLWriter 组件来构建要返回给客户端的 XML 文档。通过两个方法实现这一点;首先最初调用 SugarRestXML::_convertArrayToXML() 来创建 XML 声明头和包含 web 服务方法调用结果的 root 结果标记。然后迭代结果数组,递归地调用 SugarRestXML::_convertArrayItemToXML() 来将结果数组的所有节点转换成正确的 XML 格式。由此产生的 XML 有效负载被返回给客户端。清单 8 显示该有效负载的一个示例。

清单 8. 从登录 web 服务调用返回的 XML 工作负载示例
<?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>

正如在您传递给服务器的 XML 有效负载中,返回的有效负载拥有关联数组的键,该数组作为 XML 文件中的标记名返回。其中键以数组作为值,作为指定的 XML 标记的子节点。整个 XML 有效负载在结果 XML 标记中返回。


调用新定义的 web 服务

现在您已经构建了可以处理和返回 XML 有效负载的自定义 web 服务,接下来在 清单 9 中看一下如何实现这一点的一个简单代码示例。

清单 9. 连接到自定义 web 服务的示例脚本,以 XML 格式来回传递数据。
<?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";

在本例中,我们调用 login web 服务方法。指定要发送给服务器的有效负载的 input_type 和您希望作为 XML 返回的有效负载的 response_type,然后在 rest_data 参数中传递实际 XML 数据。然后使用 PHP SimpleXML 库将 XML 解析成 PHP 对象,并获取该类的 id 属性,显示有关成功登录和所返回会话 id 的消息。


结束语

在本文中,您了解了使用 XML 数据结构与 web 服务交互的持续价值,也就是说 XML 能够有效处理大型数据集,且允许您轻松将返回的数据转换成不同格式。然后看了通过创建自定义切入点对 SugarCRM web 服务使用 XML 有效负载的代码。您也看了如何编写代码来接受传入的 XML 有效负载并处理它,以便 web 服务方法可以使用 XML 数据,以及如何编写代码来接受方法返回数组并将其更改为一个 XML 文档返回给客户端。最后是一个示例脚本,展示如何使用 XML 数据调用 SugarCRM web 服务。


下载

描述名字大小
文章源代码sugarcrmxmlrest.source.zip6KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, SOA and web services, Open source
ArticleID=678424
ArticleTitle=扩展 SugarCRM REST web 服务以使用 XML
publish-date=06072011