메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

PHP를 활용한 플렉스 애플리케이션의 데이터 처리

한빛미디어

|

2010-08-13

|

by HANBIT

12,457

제공 : 한빛 네트워크
저자 : Robert Bak
역자 : 이대엽
원문 : PHP as a data source for Flex applications

요즘 많은 사람들이 웹 페이지 개발하는 것에서 RIA를 구축하는 것으로 옮겨가는 듯 합니다. 하지만 웹 개발이라는 맥락에서 플렉스나 실버라이트, JavaFX와 같은 이름을 달고 점점 더 자주 나타나고 있는 새로운 클라이언트 측 기술이 출현하는 것은 쉽게 알아차릴 수 있지만, RIA로 나아가는 움직임은 실제로 서버 측 코드를 작성하는 이들에게도 영향을 줍니다.

변화하는 PHP의 역할

사용자 인터페이스로 RIA를 구축했다면 더는 서버에서 데이터와 시각적 요소를 혼합할 필요가 없습니다. 이는 PHP 코드를 작성하는 사람들이 이제는 데이터에 형태를 제어하거나 정보를 표나 div, 또는 다른 태그로 감쌀 필요가 없다는 뜻입니다. 그들은 데이터를 어떤 유용한 형태로 돌려 보내기만 하면 되는데, 다행히도 플렉스에는 XML가 지원됩니다. 플렉스와 PHP가 통신하는 데 필요한 코드를 살펴봅시다. 서버 측 코드에서는 데이터베이스에서 데이터를 가져와 XML 태그로 감싼 다음 되돌려 보내야 하며, 다음은 그러한 일을 하는 핵심적인 코드입니다.
// file:DAO/BookDAO.php
 connection = mysql_connect(config::$host,config::$login,
                                             config::$password);
          mysql_select_db( config::$database );
      }
 
     /* 모든 책을 XML 형식으로 반환 */
     function getAllXML() {
          $sql = "SELECT * FROM books";
          $result = mysql_query( $sql, $this -> connection );
          $xml = "";
          while( $row = mysql_fetch_array( $result ) ) {
             $xml .= $this->rowToXml($row);
           }
          $xml .= "";
          return $xml;
      }
 
     /* 데이터베이스 행을 매개변수로 취해 XML 형태의 책 노드를 반환 */
     function rowToXml($row)
     {
           $xml .= "";
           $xml .= "".$row["id"]."";
           $xml .= "".$row["author"]."";
           $xml .= "".$row["title"]."";
           $xml .= "".$row["genre"]."";
           $xml .= "".$row["price"]."";
           $xml .= "".$row["publish_date"]."";
           $xml .= "".$row["description"]."";
           $xml .= "";
           return $xml;
      }
} ?>
이 클래스 외에도 모든 데이터베이스 설정 데이터가 포함된 파일(config.php)과 getAllXML 함수를 호출한 다음 결과를 출력하는 파일도 있습니다.
// file:sample1.php
 getAllXML());
?>
위 코드는 다소 기초적인 PHP 코드이므로 이제 플렉스로 넘어가겠습니다. 책 목록을 제공하는 서비스가 만들어졌다면 이제 해당 책 목록에 접근해서 멋진 표로 보여주는 코드를 작성하겠습니다. 다음 코드를 붙여 넣은 다은 이 코드에서 어떤 일이 일어나는지 설명해 드리죠.
        
        
                
  
    
      
      
      
    
  

위 코드에는 두 가지 컴포넌트가 있습니다. 첫 번째 컴포넌트는 HTTPService라는 것으로 짐작하고 있겠지만 HTTP 상으로 데이터를 내려 받는 데 사용됩니다. XML을 가져올 때 결과 형식으로 왜 "e4x"를 집어넣었는지 궁금할 겁니다. E4X는 "ECMAScript for XML"로서 여기서는 데이터를 더 다루기 쉬운 네이티브 플렉스 XML 클래스로 처리하고 싶다는 뜻입니다. 두 번째 컴포넌트는 열이 몇 개 포함돼 있는 DataGrid라는 것으로, sampleService에서 반환한 데이터에 책이 들어있는지 확인합니다. 모든 일은 애플리케이션 작성이 완료되고 sampleService가 서버로 요청을 보낼 때 일어납니다. 요청 결과는 다음과 같은 모양의 자그마한 RIA로 나타납니다.

그림1

고작 몇 줄 작성한 것 치고는 결과가 나쁘지 않지만 좀더 개선해 보겠습니다.

데이터 형식 - XML과 AMF

XML은 정말 좋고, 사용하거나 만들기 쉬우며, 어떠한 프로그래밍 언어에서도 파싱할 수 있고, 사람이 읽을 수도 있습니다. 하지만 여기엔 그만한 대가가 따릅니다. XML은 전송하고자 하는 정보의 양을 고민해야 할 때에는 적절한 데이터 형식이 아니며, 어떤 두 소프트웨어가 데이터를 텍스트로 변환한 다음 태그로 감싸지 않고도 상호 통신이 가능하다면 XML을 쓰는 것은 낭비라 느껴집니다. 다행히도 플렉스와 PHP는 AMF라는 이진 데이터 형식을 지원합니다. 그리고 AMF는 데이터를 전송할 때 매우 효율적이어서 분명 마음에 드실 겁니다. 직접 눈으로 봐야 직성이 풀린다면 제임스 워드(James Ward)가 다양한 데이터 형식을 이용해서 큰 데이터 집합을 보낼 때 사용되는 대역폭 테스트 애플리케이션을 제작했고, 그 결과를 여기에서 확인하실 수 있습니다. 젠드 프레임워크(Zend Framework) 1.7 버전부터 AMF 데이터를 전송할 수 있는 zend_amf가 포함됐습니다. 이어서 살펴볼 예제가 동작하려면 서버에 zend_amf를 설치해야 하며, 아직 설치하지 않았다면 젠드 웹 사이트에서 무료로 내려 받으면 됩니다.

XML에서 AMF로 갈아타기

먼저 앞에서 사용한 XML 코드에서 XML 대신 객체를 사용하도록 변경합니다. 코드를 변경하는 것은 그리 어렵지 않으며, 아래의 BookDAO와 이전에 사용한 BookDAO를 비교해보면 크게 바뀐 것이 없음을 알 수 있습니다.
// file:DAO/BookDAO.php
 connection = mysql_connect(config::$host,config::$login,
                                             config::$password);
          mysql_select_db( config::$database );
      }
 
     /* 모든 책을 객체의 배열로 반환 */
     function getAll()
     {
          $array = array();
          $sql = "SELECT * FROM books";
          $result = mysql_query( $sql, $this -> connection );
          while( $row = mysql_fetch_array( $result ) ) {
              array_push($array,$this->rowToBook($row));
           }
          return $array;
      }
 
     /* 데이터베이스 행을 매개변수로 취해 객체를 반환 */
     private function rowToBook($row)
     {
          $book;
          $book->id = $row["id"];
          $book->author   = $row["author"];
          $book->title    = $row["title"];
          $book->genre    = $row["genre"];
          $book->price    = $row["price"];
          $book->publish_date = $row["publish_date"];
          $book->description  = $row["description"];
          return $book;
      }
} ?>
이렇게 변경했다면 벌써 반은 고친 셈입니다. 이제 sample1.php를 AMF 서버로 노출되는 다른 뭔가로 대체해야 합니다.
// file:sampleamf.php
setProduction(false);
    // BookDAO 클래스를 바깥 세상으로 공개
    $server->setClass("BookDAO");

    // 요청을 처리
    echo($server->handle()); 
?>
코드 안의 주석이 코드를 이해하는 데 부족함이 없었으면 좋겠습니다. 브라우저에서 이 파일이 위치한 곳으로 가면 모든 것이 동작하고 빈 페이지에 "Zend Amf Endpoint"라고 적힌 것을 볼 수 있을 겁니다.

다음으로 할 일은 자그마한 플렉스 애플리케이션을 수정해서 새로 생성된 서비스로부터 데이터를 가져오는 겁니다. 이번에 변경할 사항도 간단하긴 하지만 약간 덜 직관적입니다. 새 소스 코드를 보시죠.


  
    
  
  
  
    
  
  
  
    
      
      
      
    
  

데이터를 전과 같은 방식으로 보여주고 싶으므로 데이터그리드는 변경하지 않았습니다. 정확히 여기서 어떤 일이 일어나는지를 설명하는 것은 이 기사의 범위를 벗어나지만, 여기서는 대략 다음과 같은 일을 해야 합니다.
  • 요청을 처리할 파일을 가리키는 AMFChannel을 정의한다.
  • RemoteObject는 개별 채널을 인자로 취하지 않으므로 1에서 정의한 AMFChannel을 ChannelSet에 집어넣는다.
  • HTTPService 대신 RemoteObject를 생성한다.
지금까지 항상 GET이나 POST 매개변수만 썼다면 RemoteObject는 약간 이상하게 보일 겁니다. RemoteObject는 특정 객체(여기서는 BookDAO)에 대한 일종의 연결 지점을 만들어서 로컬 파일인 것마냥 해당 객체의 함수를 호출할 수 있게 만들어 줍니다. 또한 여기서는 url을 곧바로 입력하지 않고, 대신 위에서 생성한 channelSet을 사용합니다. destination은 zend_amf를 사용하고 있을 경우 "zend"로 설정해야 합니다. 그러고 나면 한쪽에는 RemoteObject가 있고 다른 한쪽에는 PHP의 BookDAO.php가 놓인 상태가 됩니다. "source"는 연결 지점을 생성할 클래스를 정의하고, Method는 해당 클래스에 들어 있는 함수 하나를 가리킵니다(mx:Method 태그는 하나 이상 포함할 수 있습니다). 아래 그림은 여기서 어떤 일이 일어나는지 간략히 보여줍니다.

그림2

아마 getAll 메서드가 결과를 돌려 받았을 때 뭔가 한다는 것을 눈치 챘을지도 모르겠습니다. 이 메서드는 획득한 데이터를 데이터그리드의 데이터 소스로 설정하는데, 이렇게 하면 이전과 정확히 같은 일을 하지만 상당한 대역폭을 절약하는 애플리케이션이 만들어집니다.

보다시피 PHP 코드 습관을 바꾸는 것은 거창하거나 복잡한 일이 아니며, 대역폭을 절약하는 것은 AMF를 이용했을 때 가장 먼저 얻게 되는 한 가지 혜택에 불과합니다.

데이터 모델 구성

그럼 지금부터 대역폭을 절약해 봅시다. 하지만 사용자 시간을 줄이는 것은 차치하더라도 AMF를 이용하면 코딩하는 시간도 줄일 수 있습니다. PHP와 액션스크립트 모두 특정 클래스를 지정할 필요 없이 네트워크를 통해 데이터를 전송할 수 있지만 바로 위 예제에서 했던 것처럼 그렇게 하는 것이 가장 좋은 방법은 아닐지도 모릅니다. 제가 이렇게 말하는 주된 이유는 일종의 데이터 계약을 제공한다는 것 때문입니다. 플렉스 애플리케이션과 백엔드를 작성하는 사람이 동일 인물이 아닌 경우를 떠올려 보십시오. 그런 경우에는 서버에서 데이터를 받기 전에 해당 데이터의 형태가 어떨지 미리 알 수 있다면 굉장히 도움될 겁니다. 이를 위해 보통 값 객체(Value Object), 또는 데이터 전송 객체(DTO, Data Transfer Object)라고 하는 클래스를 양측에 동일하게 만들어서 처리하곤 합니다. 하지만 AMF를 이용하면 부가적인 기능을 하나 더 얻을 수 있습니다. 바로 매핑을 통해 직렬화나 역직렬화 코드를 작성하지 않고도 데이터가 서로 대응되는 PHP와 액션스크립트 객체 간에 자동으로 변환되게 하는 것입니다. 값 객체를 살펴보시죠.
// file:vo/BookVO.php

// file:vo/BookVO.as
package vo
{
   [RemoteClass(alias="BookVO")]
   public class BookVO
   {
      public var id:int;
      public var author:String;
      public var title:String;
      public var genre:String;
      public var price:int;
      public var publish_date:String;
      public var description:String;    
    }
}
여기서 몇 가지 알아야 할 사항이 있습니다. 첫 번째 액션스크립트 변수에서는 PHP와 달리 타입을 선언했습니다. PHP에는 명시적으로 변수의 타입을 선언하지 못하는 문제가 있으므로 데이터 계약을 정의할 때 PHP 변수의 데이터 타입을 주석으로 유지해 두면 도움이 됩니다. 또 다른 차이점은 액션스크립트의 타입 선언 위에 있는 RemoteClass라는 메타데이터 태그입니다. 이 부분이 바로 매핑 정의가 처음 시작하는 부분이며, "BookVO"로 표시된 채로 PHP로부터 전달된 객체는 이 클래스로 변환되리라는 것을 말해줍니다. PHP에서는 태그를 사용하지 않는 대신 sampleamf.php에 다음과 같이 한 줄을 추가해줘야 합니다.
$server->setClassMap("BookVO","BookVO");
아울러 PHP에서도 새로 생성된 클래스를 사용하고 싶을 것이므로 rowToBook 함수의 첫 줄을 아래와 같이 변경합니다.
$book = new BookVO();
이제 getAll의 결과가 BookVO의 배열로 반환될 겁니다. 그럼 남은 일은 플렉스 프로젝트에 액션스크립트 쪽 변경 사항을 반영하는 것뿐입니다. 플렉스 컴파일러에는 컴파일된 swf에서 사용되지 않는 클래스는 포함되지 않으므로 해당 클래스를 강제로 포함시키는 방법은 주 파일에 다음과 같이 사용되지 않는 변수를 추가하는 것입니다.
private var bookDummy:BookVO;
대부분의 경우에는 이런 방법이 불필요한데, 이는 일반 프로젝트에서는 그러한 클래스를 어딘가에서 사용할 수도 있을 것이기 때문입니다. 하지만 이런 이유로 매핑이 실패할 수도 있음을 알아둘 필요가 있습니다.

지연 적재

이제 마지막으로 신경 써야 할 부분은 전송된 데이터의 양입니다. XML 대신 AMF를 이용하면 데이터의 양이 훨씬 적긴 하지만 데이터베이스에 존재하는 항목의 수가 늘어나는데 비례해서 데이터의 양도 많아질 것입니다. 게다가 사용자가 처음 몇 개의 항목만 읽는다고 해도 해당 항목을 살펴보기 전에 모두 내려 받아야 할 것입니다. 웹 상에서 이를 해결하는 일반적인 방법은 페이지 처리를 이용하는 것이며, 페이지 처리에 관해서는 별달리 설명할 필요가 없을 겁니다. 하지만 애플리케이션에서 페이지 처리를 할 때는 데이터를 이용하기가 영 불편하고, 오히려 스크롤을 이용하는 편이 더 낫지 않을까요? 이 기사의 나머지 부분에서는 이미 모든 데이터가 애플리케이션에 들어 있고 데이터를 따로 내려 받지 않는 것처럼 사용자가 쉽게 애플리케이션을 이용하게 만들어 주는 단순한 방법을 보여드리겠습니다. 이때 사용되는 기법을 "지연 적재(lazy loading)"라고 하며, 이 기법은 사용자가 실제로 데이터가 필요할 때 필요한 해당 데이터만을 내려 받는다는 착상에 토대를 둡니다.

"지연 적재"를 구현하기 위해 해야 할 일은 먼저 DB에 저장된 책의 수를 파악하는 것입니다. 그러고 나서 책의 총 개수와 정확히 같은 길이의 ArrayCollection을 생성하고 DataGrid에 dataProvider로 삽입합니다. DataGrid에서 볼 수 있을 행의 값만을 내려 받고 난 다음 그것들을 ArrayCollection의 적절한 빈 공간에 집어넣습니다. 이 같은 구현은 단순함을 위해 사용자가 데이터를 사용할 때 해당 데이터가 변경되지 않는다는 데 가정을 둡니다

여기서 변경된 사항은 그리 많지 않으므로 바로 PHP 코딩을 시작하겠습니다. 여기서는 함수가 두 개 필요한데, 하나는 항목의 개수를 가져오는 함수로서 이해하기가 어렵지 않습니다.
    function getCount()
    {
         $array = array();
         $sql = "SELECT COUNT(*) FROM books";
         $result = mysql_query( $sql, $this -> connection );
         $row = mysql_fetch_array( $result );
         return $row[0];
     }
그리고 다른 하나는 MySQL의 LIMIT 명령을 이용해서 전체 데이터를 가져오는 대신 데이터의 일부를 가져오는 함수입니다.
    function getSome($minIndex,$maxIndex)
    {
         $array = array();
         $sql = "SELECT * FROM books LIMIT ".$minIndex.",".$maxIndex;
         $result = mysql_query( $sql, $this -> connection );
         while( $row = mysql_fetch_array( $result ) ) {
             array_push($array,$this->rowToBook($row));
          }
         return $array;
     }
플렉스 측에서는 데이터를 관리할 책임이 있으므로 변경된 사항이 꽤 많습니다. 앞서 작성한 코드에서 뭐가 바뀌었는지 살펴보시죠.

    
        

  

    
        
        
        
    

  

    

가장 먼저 변경된 사항으로는 이제 "getAll" 함수를 쓸 일이 없고, 대신 새로 만든 두 함수를 사용할 것이므로 RemoteObject 메서드를 재정의한 것입니다. 다음으로는 앞으로 채울 dataProvider와 DataGrid에 대한 스크롤 핸들러를 정의한 것입니다. 바로 이곳에서 사용자가 스크롤을 내릴 때마다 새로운 데이터를 내려 받아야 할지 검사할 겁니다.

RemoteObject 결과를 처리하는 두 함수는 아래에서 보다시피 모두 간단합니다.
protected function getCountResult(event:ResultEvent):void
{
     dataProvider = new ArrayCollection(new Array(int(event.result)));
     remoteObject.getSome(0,dataGrid.rowCount);
}

protected function getSomeResult(event:ResultEvent):void
{
     for (var i:int = event.token.message.body[0]; i
데이터의 양을 알고 나면 dataProvider를 초기화하고 dataProvider의 길이를 미리 알아낸 크기로 설정합니다. 그러고 나면 dataGrid에서 보여줄 첫 번째 줄로 시작해서 마지막 줄로 끝나는 데이터의 첫 번째 조각을 내려 받습니다. 호출이 완료되면 getSomeResult 함수에서 dataProvider를 DB에서 방금 가져온 항목으로 설정합니다. 이 함수에서는 getSome 함수를 호출할 때 사용한 매개변수를 이용해서 항목을 적절한 위치에 집어 넣습니다(event.token.message.body에 그러한 매개변수가 담겨 있습니다).


이제 남은 일은 DataGrid 스크롤바의 변경 사항을 처리하고 누락된 데이터를 내려 받는 것입니다. 이를 좀더 효율적으로 처리하기 위해 매번 호출하기 전에 확인 과정을 거치고 이전에 내려 받이지 않은 행만 내려 받이게끔 범위를 조정할 것이며, 이 같은 작업은 checkWhichAreMissing 함수에서 이뤄집니다.
protected function dataGrid_scrollHandler(event:ScrollEvent):void
{
     if (dataProvider==null) return;
     var correctedRange:Array = checkWhichAreMissing(event.position,event.position+dataGrid.rowCount);
     if (correctedRange[0]from)&&(dataProvider.getItemAt(to-1)!=null))   to--;
     return [from,to];  
}
이렇게 하고 나면 사용자가 필요로 하는 데이터만 내려 받는, 완전히 동작하는 애플리케이션이 만들어집니다. 이 프로젝트에 사용한 추가적인 주석이 달려 있는 모든 파일은 이 기사의 마지막에 나와 있는 링크에서 내려 받을 수 있습니다. 바라건대 여기서 작성한 코드가 플렉스와 PHP에서 제공하는 가능성을 탐구하는 적절한 출발점이 되기를 바랍니다.

소스코드 다운로드

SourceFiles.zip
TAG :
댓글 입력
자료실

최근 본 책0