JSON(이는 JavaScript Object Notation의 약어임) 및 일련화된 PHP 데이터의 지원을 통해 XML이 필요한 이유가 무엇인가? 이 질문은 SugarCRM에서 웹 서비스에 특별하지 않다. 즉, 점점 더 많은 REST 스타일 웹 서비스는 JSON이 더 경량화되었고, 인코딩 데이터에 대한 지원이 더 간편하며 풍성한 Ajax 애플리케이션으로 해당 페이로드를 직접 소모하는 기능을 이유로 더 구조화된 XML 데이터 형식보다 JSON을 사용하는 것을 선호한다. 하지만 XML이 절정기일 때에 가장 중요한 것으로 고려되지 않았던 것처럼 JSON도 웹에 걸쳐 통신하기 위해 가장 중요한 것이 아니다. XML보다 JSON 표기법을 사용하면 몇 가지 유리한 점이 있다고 하더라도, 여전히 XML이 JSON이나 일련화된 PHP보다 선호되는 데이터 형식인 몇 가지 유스 케이스들이 있다.
- 데이터 세트가 대규모라면 한 번에 데이터를 구문 분석하는 것이 비용이 매우 많이 들 수 있다. 대규모 데이터 세트를 JSON 또는 일련화된 PHP로 수행하는 데 필요한 대로 메모리에서 한 번에 전체 데이터 구조를 모두 로드하는 것이 아니라 XML 풀(pull) 구문 분석기로 더 효율적으로 처리할 수 있다.
- XML 데이터는 원하는 어느 표시장치 형식으로나 XSLT를 사용하여 간편하게 변환될 수 있다. 예를 들어, 웹 서비스 호출의 결과를 취할 수 있고, 그 다음에 사용자에게 표시될 수 있는 HTML 문서를 작성하기 위해 XSLT 변환을 수행할 수 있다.
- 많은 레거시 애플리케이션과 프로그래밍 언어는 XML에 대한 네이티브 지원이 있지만, 더 새로운 기술이기 때문에 JSON에 대한 지원은 보유하지 않는다. 그리고 독자가 PHP 외의 다른 것을 사용하는 중이라면, 일련화된 PHP 데이터 처리는 독자의 선택사항이 아니다.
많은 사람들은 XML이 기술로서 특이하다고 생각하는 반면에, 그 대신에 이는 이전에 개괄한 틈새 유스 케이스를 더 원활하게 처리할 수 있게 되었다. 입력 및 출력 데이터 형식으로 이 기술에 대한 지원은 SugarCRM 웹 서비스의 일부로 보유하는 것이 유용하다.
사용자 정의 시작점을 SugarCRM 웹 서비스 API로 추가하여 시작하자. 이 시작점은 클라이언트가 SugarCRM 웹 서비스 메소드를 실행하기 위해 HTTP POST에 걸쳐 호출하는 URL이다. 독자가 기존 웹 서비스를 변경하려 하지 않기 때문에 대신에 custom/service/v2/rest.php에 있는 사용자 정의 디렉토리에서 정의된 시작점을 통해 새 시작점을 v2 웹 서비스(시작점 service/v2/rest.php를 통해 액세스됨)로 추가한다. 목록 1은 이 시작점의 컨텐츠를 보여준다.
목록 1. 웹 서비스 시작점 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에서 시작점은 변수를 지정하며, 이는 웹 서비스 시작점 정의의 일부로 설정해야 하는 다양한 특성을 표시한다. 이 예제에 대해 참고할 핵심적인 것은 $webservice_class와
$webservice_path이며, 이는 클래스와 경로의 이름을 각각 지정한다. 이 클래스는 webservice에 작성한 수신 요청을 처리하고
적절한 웹 서비스 메소드를 호출한다. 이는 또한 웹 서비스 메소드에서부터 결과를 취하고 페이로드를 클라이언트에게 적절하게 리턴하기 위해 이를 패키지화한다. 독자는 또한 특성
$location에서 이 시작점으로 경로를 지정한다.
이제 webservice 클래스를 분석해보자. 이는 custom/service/core/CustomSugarRestService.php에 있다. 목록 2는 클래스 정의를 보여준다.
목록 2.
CustomSugarRestService 웹 서비스 핸들러 클래스
<?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() 메소드를 오버라이드하여, 다음 섹션에서 정의하는 SugarRestXML 클래스를 사용하기 위해
유형 XML을 맵핑하는 기능을 추가한다. 또한 응답 클래스를 인스턴스화하기 위해
서버 클래스와 SugarRestServiceXML::serve() 메소드를 인스턴스화하는 생성자도 오버라이드한다. 이러한 마지막 두 가지 변경이 필요하므로 메소드는
service/core/REST/ 디렉토리에서가 아니라 다른 SugarRest 구현을 위해 custom/service/core/REST/ 디렉토리에서 살펴보도록 인식한다.
이제 사용자 정의 웹 서비스에 대해 시작점을 정의하였고 webservice 클래스에서 필요한 훅을 추가했으니, 사용자 정의 디렉토리에서
SugarRest 구현을 찾을 수 있고 웹 서비스로 사용되는 XML 데이터를 처리하기 위한 클래스를 정의하도록 시작할 수 있다. 이제
SugarRestXML이라는 이름의 새 클래스를 정의한다. 이는 SugarRest 클래스의 서브클래스이고 수신 XML 페이로드를 처리하고
적절한 XML 형식에서 클라이언트로 결과를 다시 리턴하기 위해 필요한 조각을 제공한다.
수신 XML 페이로드를 처리하는 데 사용되는 메소드를 정의하는 방정식의 첫 번째 부분을 살펴보자. 이 태스크를 처리하는 기본 메소드는 SugarRestXML::serve()이며, 이는 목록 3에 정의된다.
목록 3. SugarRestXML.php 웹 서비스 요청 처리하기
<?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);
}
}
|
이 메소드는 매우 기본적이며, 이는 설정된 method 및 rest_data의 예상된 REQUEST 변수를 찾는다. 매개변수 method는
웹 서비스 메소드가 호출하도록 지정하고, rest_data는 메소드로 전달되는 XML 페이로드가 들어있다. 수신 요청의 XML 형식은 태그 parameters 아래
항목으로 메소드로 전달되는 각 매개변수가 있다. 어느 매개변수나 입력으로서 배열을 허용하면, 이는 매개변수의 태그 내에 포함된 태그가 되어 "XML화"될 것이다. 예를 들어, SugarCRM 인스턴스로
로그인하려면 목록 4에 나열된 XML을 login 웹 서비스 메소드로 전달할 수 있다.
목록 4. 로그인 웹 서비스 메소드로 향하는 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 배열로 변환하고 웹 서비스 메소드를 구현하는 내부 메소드로 전달한다. 이를 수행하기 위해 간단한 메소드를 추가한다. 이는
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 문서를 통해 작업한다. 그 다음에 이 배열은 목록 3에서
call_user_func_array() 함수를 사용하여 호출하는 웹 서비스 메소드로 전달된다.
이제 수신 웹 서비스 요청의 XML 페이로드를 처리하기 위해 방법을 빌드하였으니, 이제 XML 응답을 클라이언트로 리턴하는 방법을 빌드한다. 이 단계는
목록 6과 같이 SugarRest::generateResponse() 및 SugarRest::generateFaultResponse() 메소드를 오버라이드하는 것을 포함한다.
목록 6. SugarRestXML.php 웹 서비스 응답 처리하기
/**
* @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()는 웹 서비스 메소드가 사용자에게 리턴하기 위한 컨텐츠를 보유한 후에 호출된다. 이는 먼저 해당 사용자에게 리턴된 컨텐츠가 결함 오브젝트를
표현하는지 여부를 보기 위해 확인한다. 이는 사용자에게 오류를 리턴하는 것을 의미한다. 그러한 경우, 이는 대신에 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);
}
}
}
|
여기에서 독자는 클라이언트로 리턴하기 위해 XML 문서를 빌드하도록 XMLWriter 컴포넌트를 활용한다. 이를 다음 두 가지 메소드에 걸쳐 수행한다. 하나는
XML 선언 헤더와 웹 서비스 메소드 호출로부터 결과가 포함된 루트 결과 태그를 작성하기 위해 SugarRestXML::_convertArrayToXML()을 처음부터 호출한다. 그 다음에, 결과 배열을
반복하여, 적절한 XML 양식으로 결과 배열의 모든 노드를 변환하기 위해 반복적으로 SugarRestXML::_convertArrayItemToXML()을 호출한다. 결과 XML 페이로드는 클라이언트에게
다시 리턴된다. 목록 8은 이 페이로드의 예제를 보여준다.
목록 8. 로그인 웹 서비스 호출로부터 리턴된 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 태그에 리턴된다.
이제 XML 페이로드를 처리하고 리턴할 수 있는 사용자 정의 웹 서비스를 빌드하였으니, 목록 9에서 이를 수행하는 방법의 간단한 코드 예제를 살펴보자.
목록 9. 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 웹 서비스 메소드를 호출한다. 서버로 전송한 페이로드에 대해 input_type 및 XML로 리턴하는 것으로 예상하는 페이로드로 response_type을
지정한 다음에, rest_data 매개변수에서 실제 XML 데이터를 전달한다. 그 다음에 독자는 PHP
SimpleXML 라이브러리를 사용하여 XML을 PHP 오브젝트로 구문 분석하고 해당 클래스로부터 ID 속성을 취하여 성공적인 로그인에 대한 메시지와 리턴된 세션 ID를 표시한다.
이 기사에서 웹 서비스와 상호작용하기 위해 XML 데이터 구조를 사용하는 것의 지속적인 가치, 즉, 대규모 데이터 세트를 효율적으로 처리하고 다른 형식으로 편리하게 리턴된 데이터를 변환하도록 허용하는 기능에 대해 배웠다. 그 다음에 사용자 정의 시작점을 작성하여 SugarCRM 웹 서비스로 XML 페이로드를 사용하는 기능을 추가하기 위해 해당 코드를 살펴보았다. 또한 수신 XML 페이로드를 취하여 이를 처리하는 코드도 살펴보았으므로, 웹 서비스 메소드는 메소드 리턴 배열을 취하고 클라이언트에게 전달하기 위해 이를 XML 문서로 변경하도록 코드와 XML 데이터를 사용할 수 있다. 마지막으로 XML 데이터를 사용하여 SugarCRM 웹 서비스를 호출하는 방법을 보여주는 예제 스크립트를 확인했다.
| 설명 | 이름 | 크기 | 다운로드 방식 |
|---|---|---|---|
| article source code | sugarcrmxmlrest.source.zip | 6KB | HTTP |
교육
- SugarCRM Developer Documentation:
다양한 SugarCRM API에 대한 자세한 안내서를 확인해보자.
- SugarCRM Developer Zone: 모든 SugarCRM 개발자를 위한 컨텐츠를 탐색해보자.
- The Definitive Guide to SugarCRM: Better Business Applications:
SugarCRM을 기반으로 하는 애플리케이션을 개발하기 위한 훌륭한 안내서를 읽어보자.
- Build a RESTful web service(Andrew Glover저, developerWorks, 2008년 7월):
이 단계별 튜토리얼에서 REST 및 Restlets로 애플리케이션을 빌드하는 것의 기초적인 개념에 대해 배워보자.
- Learning
PHP 시리즈(Nicholas Chase 및 Tyler Anderson저, developerWorks, 2005년 6월-7월): PHP로 프로그램하기 위해 학습하는 것에 대한 이러한 튜토리얼을 확인하자.
- PHP.net: 널리 사용되는 일반 용도 스크립팅 언어를 위한 PHP 문서를 검토하자.
- Recommended PHP reading list:
IBM 웹 애플리케이션 개발자들이 프로그래머와 관리자를 위해 컴파일한 이러한 읽기 목록으로 PHP(Hypertext Preprocessor)에 대해 배워보자.
- PHP content on developerWorks:
광범위한 기술 기사, 팁, 튜토리얼 및 기타 자원에 대한 오픈 소스 라이브러리를 찾아보자.
- PHP project resources: IBM developerWorks 자원을 확인하여 PHP 기술을 향상시키자.
- XML 1.0
Specification(W3C Recommendation, 2008년 11월): CDATA 섹션과 같이 XML 기능에 대한 특정 세부사항은 이 소스를 읽어보자.
- XML FAQ: Peter Flynn이 편집한 XML 정보의 또다른 우수한 소스를 살펴보자.
- W3schools.com의 XML DOM Tutorial: 브라우저에 사용할 수 있는 XML 기반 인터페이스와 이러한 인터페이스를 지원하는 브라우저에 대해 알아보자.
- XHTML™ 1.0: The Extensible HyperText Markup Language(World Wide Web Consortium Recommendation, 2000년 1월 26일):
재생산하는 현재와 미래 문서 유형 및 모듈 세트, 서브세트에 대해 자세히 읽어보고 HTML 4를 확장하자.
- developerWorks의 XML 영역: XML 분야의 기술을 향상시키는 데 도움이 되는 참고자료를 얻을 수 있다.
- developerWorks 오픈 소스 영역: 오픈 소스 기술로 개발하고 이러한 기술을 IBM 제품과 함께
사용하는 데 도움이 되는 광범위한 사용법 정보, 도구 및 프로젝트 업데이트를 찾아보자.
- My developerWorks: developerWorks와 관련된 경험을 개인화할 수 있다.
- IBM XML 인증: XML 및 관련 기술에 대한 IBM 인증 개발자가 되는 방법을 찾아볼 수 있다.
- XML 기술 자료: developerWorks XML 영역 라이브러리에서 다양한 기술 관련 기사와 팁, 튜토리얼, 표준 및 IBM Redbook을 볼 수 있다. 또한 더 많은 XML 팁을 읽어본다.
- developerWorks 기술 행사 및 웹 캐스트: 이러한 세션에 참가하여 최신 기술에 대한 정보를 얻을 수 있다.
- Twitter의 developerWorks 페이지: 오늘 가입하여 developerWorks 트윗을 팔로우하자.
- developerWorks
podcasts: 소프트웨어 개발자의 흥미로운 인터뷰와 토론을 확인할 수 있다.
- developerWorks on-demand demos: 입문자를 위한 제품 설치 및 설정 과정에서 숙련된 개발자를 위한 고급 기능의 활용에 이르기까지 다양한 데모를 제공한다.
제품 및 기술 얻기
- IBM 제품 평가판: IBM SQA Sandbox의 온라인 시험판을 다운로드하거나 살펴보고
DB2®, Lotus®, Rational®, Tivoli® 및 WebSphere® 애플리케이션 개발 도구 및 미들웨어 제품을 사용해 볼 수 있다.
토론
- XML 영역 토론 포럼: 여러 XML 관련 토론에 참여해 볼 수 있다.
- developerWorks 커뮤니티: 개발자가 운영하고 있는 블로그,
포럼, 그룹 및 위키를 살펴보면서 다른 developerWorks 사용자와 의견을 나눌 수 있다.
John Mertic은 SugarCRM의 소프트웨어 엔지니어이며, PHP 웹 애플리케이션과 관련하여 수 년간의 경험이 있다. 그는 SugarCRM에서 데이터 통합, 모바일 및 사용자 인터페이스 아키텍처를 전문으로 하여 재직 중이다. 왕성한 저술가로서 php|architect, IBM developerworks 및 Apple Developer Connector에 기고했으며, "The Definitive Guide to SugarCRM: Better Business Applications"이라는 책의 저자이기도 하다. 많은 오픈 소스 프로젝트에 참여했으며, 특히 PHP 프로젝트에 주력하고 있다. 또한 PHP Windows Installer를 개발 및 유지보수한다