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

한빛출판네트워크

IT/모바일

WASP을 이용한 PHP 개발

한빛미디어

|

2006-01-31

|

by HANBIT

14,145

원문: http://www.onlamp.com/pub/a/php/2006/01/19/wasp_intro.html
저자: Brian Fioca, 한동훈 역

WASP(와습, Web Application Structure for PHP)는 PHP5로 구축된 3-티어(tier) 프레임워크다. 최근에, 많은 소프트웨어 엔지니어들이 자바, C# 같이 번거로운 엔터프라이즈 언어에서 파이썬, 루비, PHP 같은 언어로 옮겨가고 있다. PHP5에 이르러서 개발자들이 해커들의 언어를 사용하고 있는 것과 같다고 느낄만한 위치에 도달했다. PHP5에서 복잡한 "엔터프라이즈 클래스" 프레임워크를 효율적으로 만들어내고, 사용하는 시연을 통해 보다 많은 개발자들이 WASP으로 바꾸는 데 도움이 되기 바란다.

WASP의 시초는 "PHP5에서 3-Tier 개발"였으며, 그 이후로 PHP 프레임워크와 나머지 것들, 전통적으로 사용되고 있던 3-tier 도구들 사이의 간격을 메우기 위한 노력이 이어졌다. 팽고미디어는 몇몇 대규도 프로덕션 응용프로그램에 사용해왔다.

여기서는 간단한 데이터베이스 기반 응용프로그램을 작성하는 과정을 선보일 것이다. 여기서 작성할 응용프로그램은 간단한 작업목록 관리 프로그램이다. 이 예제는 매우 짧지만 WASP이 제공하는 강력한 기능들을 잘 설명하고 있다.

여기서 사용한 코드는 WASP 1.1을 사용하였으며, PHP5 구문을 아는 것이 도움은 되지만 꼭 알아야 할 필요는 없다. 여기서 사용된 클래스나 메서드에 대한 정보는 WASP API Documentation을 참고하기 바란다. 보다 자세한 사항을 알고 싶다면 다음이 유용할 것이다.설치 및 설정

PEAR 패키지 매니저를 사용해서 WASP을 다운로드 받는다. 보다 자세한 내용은 WASP 설치 및 설정 가이드를 보기 바란다.

다음 명령을 실행해서 WASP 프로젝트를 생성한다.

phing -buildfile PEAR_DIR/lib/php/data/WASP/build.xml wasp-project

PEAR_DIR은 PEAR 클래스가 설치된 위치이며, 다음 명령을 사용해서 위치를 알 수 있다.

$ pear config-get php_dir

설치가 끝나면 다음을 볼 수 있다.

Buildfile: /usr/local/php5/lib/php/data/WASP/build.xml
WASP > wasp-project:
Directory to create project in  >

WASP 프로젝트를 생성할 전체 경로를 입력한다. 여기서 해당 디렉터리를 생성하고 build.xml과 build.properties 파일을 복사한다.

예제를 위해서 나는 다음 경로를 사용하였다.

/Users/brianfioca/Development/projects/Todo.

생성할 디렉터리가 웹 서버 루트에서 접근할 수 있는 경로여야한다.

이제, 다음과 같이 Todo 디렉터리가 생성되었다.

|_Todo
   |_build.xml
   |_build.properties

WASP는 Phing과 build.properties 파일을 사용해서 설정을 관리한다. 응용프로그램을 생성할 때 Phing은 build.proerties 파일을 읽어들이고, 프로젝트에 사용할 올바른 설정 파일을 만들어낸다.

다음은 이 예제에서 사용할 build.properties의 기본 생성 결과다.

build.properties

###################
# FILE: build.properties
# DESC: wasp project configuration properties file
#
# The properties below are used to configure your wasp application.
# Set these properties to customize your wasp install.
#
# run phing config to regenerate the configuration when these are changed.
##
# The name of the application
app.name=       Todo
# Toggle Debug output
debug.flag=     True
# Toggle for email of error messages
email.flag=     False
session.flag=  True
# URL of the database for this application
database.url=   mysql://user:pass@localhost/todo
      
# Directory where pear packages are installed
pear.dir=       /usr/local/php5/lib/php

설정에서, app.name 속성이 Todo로 되어 있는데, 이는 응용프로그램의 이름과 빌드툴에서 사용할 모든 모듈, 코드 조각, 템플릿을 갖는 루트 디렉터리의 이름을 의미한다.(wasp 디렉터리나 app.dir 디렉터리에 지정된 위치)

app.dir 속성은 웹 루트안에 위치하는 응용프로그램의 디렉터리 위치를 의미한다. 예를 들어, app.dir을 /var/www로 설정했다고 하자. 이는 아파치 HTTP 서버가 루트 디렉터리로 생각하는 디렉터리이며, 이 디렉터리는 웹 서버 설정에 따라 적절하게 변경할 수 있다. 응용프로그램이 설치되면 /var/www/Todo에 응용프로그램이 위치한다.

pear.dir 속성을 PEAR 클래스가 있는 위치로 설정한다. DB/DataObject와 HTML/Template/Flexy가 있는 위치를 지정해야 한다. 앞에서도 언급한 것처럼 다음 명령을 실행해서 디렉터리 위치를 찾을 수 있다.

$ pear config-get php_dir

database.url 속성을 프로젝트가 사용할 데이터베이스의 URL로 설정한다. 이 예제에서는 로컬호스트에 설치된 MySQL에서 todo 데이터베이스를 생성했다고 가정하고 진행한다.

설정 파일을 다시 생성하고 싶다면 언제든지 다음 명령을 실행하면 된다.

$ phing config

데이터베이스

다음으로 설정할 것은 데이터베이스 모델이다. 여기서는 소개를 위한 것이므로 이 부분은 간단하게 설명할 것이다. 작업목록 응용프로그램에 사용할 테이블을 생성하는 SQL은 다음과 같다.

CREATE DATABASE todo;
      
CREATE TABLE `Task` (
      `TaskId` bigint(20) NOT NULL auto_increment,
      `Due` date NOT NULL,
      `Name` text NOT NULL,
      PRIMARY KEY  (`TaskId`)
);

Task 테이블은 열 3개로 되어 있다. WASP 응용프로그램에서는 모든 테이블이 id 열과 같은 기본키(Primary Key)를 가질 것을 강력하게 추천한다. TaskId는 키본키로 되어 있다. 작업기한(due) 때문에 Name은 담당자 이름 또는 작업의 이름을 의미한다.

응용프로그램 작성하기

WASP을 성공적으로 설치했고, 데이터베이스를 생성했으면 작업목록 프로그램을 위한 코드를 작성해볼 차례다.

모델, 뷰, 컨트롤러

다음 단계는 응용프로그램을 위한 모델, 컨트롤러, 뷰를 생성할 차례다. 이를 위해, build.xml에서 타겟을 만들어야 한다. 이는 Phing을 사용해서 작성할 수 있다.
여기서 사용된 용어들에 겁먹을 필요는 없다. "모델"은 데이터베이스 객체를 위한 이름에 불과하며, "뷰"는 페이지에 사용할 HTML 코드이며, "컨트롤러"는 어떤 페이지를 그릴지, 모델에서 데이터를 어떻게 다룰지 결정하는 PHP 코드에 불과하다.

응용프로그램을 생성하고, 메인 컨트롤러와 뷰 객체를 생성한다.

$ phing app

프롬프트가 나타나면 Todo를 선택한다.

위 명령은 코드가 놓일 위치와 실행할 위치를 위한 메인 응용프로그램 디렉터리를 생성하며, 응용프로그램에서 사용할 초기 모델과 컨트롤러 클래스를 생성한다.
app.dir에서 지정한 디렉터리에서부터 다음과 같은 하위 디렉터리가 생성된다.(app.dir을 지정하지 않은 경우에는 현재 디렉터리에 생성된다)

|_Todo/
   |_templates/
   |_templates_c/

Todo/ 디렉터리에 컨트롤러 클래스 TodoMainModule.php와 TodoMainIndexPage.php와 컨트롤러 index.php 파일이 생성된다.

templates/ 디렉터리는 뷰가 위치하는 곳이며, 여기에 모든 "코드 조각(chunk)"이나 템플릿 파일이 위치하게 된다. 응용프로그램에서 사용할 모든 HTML과 Flexy 코드가 이곳에 위치한다. Chunk 클래스와 .chunk 파일은 일대일 관계를 갖는다. TodoMainIndexPage는 wasp.gui.Chunk를 상속받으며, index.chunk 템플릿을 참조한다. 지금 당장 이게 이해되지 않는다고 걱정할 필요는 없다. 각 절을 진행하면서 모든 것을 이해하게 될 것이다.

templates_c/ 디렉터리는 컴파일된 Flexy 템플릿이 위치한다. 웹 서버에서 이 디렉터리에 쓰기 권한을 줘야한다.

다음은 데이터 모델 객체를 생성할 차례다.

$ phing db

디렉터리 구조는 다음과 같다.

|_Todo/
   |_db/
   |_templates/
   |_templates_c/

db/ 디렉터리는 모델 클래스를 위한 모든 것을 저장한다. 여기서는 TaskWrapper.php가 생성된다. 데이터베이스의 테이블 하나당 하나의 모델 클래스가 생성된다.
todo 항목을 생성하기 위해서는 응용프로그램에서 모듈이 필요하다. 새로운 todo "Entry" 모듈을 작성하기 위해 다음 명령을 실행한다.

$ phing module

프롬프트가 나타나면 "Entry"를 선택한다. 디렉터리 트리는 다음과 같이 된다.

|_Todo/
   |_db/
   |_templates/
   |_templates_c/
|_Entry/
   |_templates/

Entry 디렉터리에는 컨트롤러 클래스와 뷰 코드조각이 생성된다.

테스트 및 설치

계속 진행하기 전에 모든 게 제대로 설치되었는지, 뷰와 컨트롤러가 동작하는지 확인해야 한다. 기본값으로 응용프로그램과 각 모듈은 각각을 바로 조회할 수 있게 각 이름으로 된 임시 페이지를 생성해낸다.

브라우저를 실행하고 http://localhost/Todo를 방문해본다.(이 링크는 지금까지 이 글에서 설명한 대로 진행한 경우에만 유효하다.)

그림1
그림1. TodoMain Index Page

다음 메시지를 볼 수도 있다.

can not write to "compileDir", which is
    "/path/to/wasp/install/directory/templates_c/"
      Please give write and enter-rights to it

templates_c 디렉터리에 웹서버가 읽기/쓰기 권한을 제대로 설정했는지 확인해야 한다. 이 디렉터리는 컴파일된 템플릿을 저장하기 위한 디렉터리로, Flexy에서 .chunk 파일에서 생성되는 컴파일 된 PHP 코드를 저장하기 위해 사용한다.

http://localhost/Todo/Entry를 방문해서 제대로 로드되지는 확인한다.

Entry 모듈

Task Entry 페이지를 위한 코드를 작성할 차례다. 이번에는 목록에 사용할 작업을 입력하기 위한 코드다.

Todo/Entry/templates/index.chunk에 템플릿 HTML을 사용한다.

mmlt;htmlmmgt;
  mmlt;bodymmgt;
    mmlt;form name="entry" method="post"mmgt;
      mmlt;h3mmgt;Create Entrymmlt;/h3mmgt;
      mmlt;pmmgt;
        Name:mmlt;br/mmgt;
        mmlt;input type="text" name="Name"/mmgt;
      mmlt;/pmmgt;
      mmlt;pmmgt;
        Date Due (format mm/dd/yyyy):mmlt;br/mmgt;
        mmlt;input type="text" name="Due"/mmgt;
      mmlt;/pmmgt;
      mmlt;input type="submit" name="Add" value="Add"/mmgt;
    mmlt;/formmmgt;
  mmlt;/bodymmgt;
mmlt;/htmlmmgt;

Task 항목은 메인 Todo 페이지에 나타나기 때문에 코드에서 표시할 어떤 데이터도 포함하지 않아도 된다. 이 페이지는 두 개의 텍스트 입력 상자와 제출 버튼으로 된 간단한 폼이다.

Entry 페이지는 이것으로 작업이 끝났으며, 다음은 그림2와 같이 /Todo/Entry에서 결과를 확인하는 것이다.

그림2
그림2. Create Entry 페이지

페이지가 제대로 그려지는 것을 확인했으면 이제 이벤트를 추가해보자. 이 페이지에서 유일한 이벤트는 Add 버튼이 클릭되는 것 뿐이다. 다음은 이벤트 처리 코드를 수정하는 것이다. 모든 폼 처리는 Chunk 클래스의 handleEvents() 메서드에서 수행한다.

Todo/Entry/EntryIndexPage.php의 handleEvents() 메서드 부분이다.

protected function handleEvents()
   {
     // Add 버튼이 클릭되는지 확인한다
    if (Request::getParameter("Add") != null)
     {
         $oTask = new TaskWrapper();
         $oTask->fillFromRequest();
         $oTask->save();
         $this->redirect("../");
     }
   }

다음 코드가 Add 버튼이 클릭해서 Entry 페이지에서 사용자가 폼을 제출했을 때를 처리한다.

//Check for Add button presses
if (Request::getParameter("Add") != null)
{
  $oTask = new TaskWrapper();
  $oTask->fillFromRequest();
  $oTask->save();
  $this->redirect("../");
}

페이지의 텍스트박스에서 지정한 이름과 기한일에 따라 새로운 Task를 생성한다. task 테이블에 사용된 컬럼이름에 따라 텍스트 박스의 이름도 Name과 Due라고 했기 때문에 다음 코드에서는 새로운 TaskWrapper 객체를 생성하고, 폼에서 전송된 데이터로 객체를 채운다.

$oTask = new TaskWrapper();
$oTask->fillFromRequest();

작은 응용프로그램이기 때문에 여기서는 Name과 Due 필드만 데이터로 사용되었지만, 큰 테이블에서 이런 방식으로 많은 데이터를 쉽게 저장할 수 있다고 생각해보라. 마찬가지로, $oTask 객체에 필드들을 채우기 위해 요청을 분석하고, 각 필드들을 수작업으로 채울 수도 있지만, 대부분의 경우에 그런 작업은 하고 싶지 않을 것이다.

$oTask->save();

위 코드는 정확하게 생각하는 그대로 작업을 처리해준다. TaskWrapper 객체는 기본키를 갖고 있지 않기 때문에 데이터베이스에 새로운 레코드를 저장한다. 만약, 다음과 같이 클래스를 생성하고

$oTask = new TaskWrapper(12);

save()를 호출했다면 기본키가 12인 기존 레코드를 업데이트할 것이다.

$this->redirect()를 호출한다. 이는 데이터를 전송했던 페이지에서 메인 페이지로 이동하게 해준다. 메인 페이지를 작성하지 않았기 때문에 저장한 작업을 아직 볼 수는 없다.

메인 Todo 페이지

Entry 모듈 페이지를 생성한 것과 마찬가지로 메인 페이지를 생성한다. 먼저, Todo/Templates/index.chunk 템플릿을 생성한다.

  mmlt;htmlmmgt;
  mmlt;bodymmgt;
    mmlt;h3mmgt;{Title}mmlt;/h3mmgt;
    mmlt;li flexy:foreach="arTasks,key,task"mmgt;{task[Name]} - mmlt;immgt;{task[Due]}mmlt;/immgt;mmlt;/limmgt;
    mmlt;pmmgt;
      mmlt;a href="Entry/"mmgt;Add Taskmmlt;/ammgt;
    mmlt;/pmmgt;
  mmlt;/bodymmgt;
mmlt;/htmlmmgt;

Flexy 코드가 삽입된 것을 볼 수 있다. mmlt;limmgt; 태그에 Flexy의 foreach가 호출되는 부분을 눈여겨 보기 바란다. Flexy의 foreach 지시문은 배열로 전달된 값들을 루프를 돌면서 나열하라고 HTML 태그에 지시한다. 여기서 배열 이름은 arTasks이며, key와 task 파라미터는 루프에서 액세스할 변수들의 이름이다.
이는 다음 PHP 코드와 같다.

mmlt;bodymmgt;
mmlt;?php
    $arTasks = array();
    foreach ($arTasks as $key =mmgt; $task)
    {
?mmgt;
        mmlt;limmgt;mmlt;?php echo $task["Name"]; ?mmgt; - mmlt;immgt;mmlt;?php echo $task["Due"]; ?mmgt;mmlt;/immgt;mmlt;/limmgt;
mmlt;?php
    }
?mmgt;
mmlt;/bodymmgt;

WASP으로 작업할 때 얻을 수 있는 장점, PHP 코드를 삽입할 필요가 없음을 살펴보았다. 이는 대부분의 디스플레이 로직에서 HTML을 분리할 수 있게 해준다. 대부분의 HTML 에디터는 flexy를 해석할 수 있다. flexy의 태그들은 레이아웃이나 디자인을 방해하지 않고 통합되어 있다.
또한, HTML에서 사용할 수 있는 Flexy 태그 유형으로 mmlt;h3mmgt;{Title}mmlt;/h3mmgt;가 있다.

{Title}은 컨트롤러 클래스에서 사용할 수 있는 위치 지정자로, 대체될 영역을 표시한 동적 태그다. Todo/TodoMainIndexPage.php의 draw() 메서드는 다음과 같다.

   public function draw()
   {
       $this->setPlaceholder("Title", "My Task List");
       $oTasks = new TaskWrapper();
       $oTasks->findAll();
       $arTasks = array();

       while ($oTasks->next())
       {
           $arTasks[$oTasks->getId()] = $oTasks->toArray();

           // Reformat timestamp using WASP Dates utility package
           $arTasks[$oTasks->getId()]["Date"] =
               Dates::mysql2datetime($oTasks->getDue());
       }
      
       $this->setPlaceholder("arTasks", $arTasks);
       parent::draw();
   }

{Title} 위치지정자를 다음 코드를 사용해서 대체하게 된다.

$this->setPlaceholder("Title", "My Task List);

이를 사용하면 작업 목록의 이름을 동적으로 변경할 수 있다. 필요하다면, 다른 데이터베이스에서 가져온 필드에도 이를 사용할 수 있다. 코드에 직접 삽입한 문자열은 예를 들기 위한 것이다.

arTasks 위치 지정자를 채우기 위해, 데이터베이스에 있는 task 레코드에 대한 배열을 가져와야 한다. WASP의 db 모델 레이어를 사용해서 이런 데이터에 접근할 수 있다.

코드:

$oTasks = new TaskWrapper();
$oTasks->findAll();

Task 테이블을 위한 DataObjectWrapper 객체를 생성하고, 기존 레코드를 모두 검색한다.

다음 코드를 사용해서 루프를 수행할 수 있다.

while ($oTasks->next())

next() 메서드는 레코드가 더 이상 없으면 false를 반환한다.

작업 집합을 루프로 도는 동안 뷰에 보여줄 값들을 추가할 수 있다.

$arTasks[$oTasks->getId()] = $oTasks->toArray();

위 코드는 작업중인 테이블의 기본키를 사용해서 배열의 인덱스를 얻기 위해 내장 메서드 getId()를 사용한다. 데이터베이스를 역참조하면서 SQL을 생성하고, TaskId 컬럼의 값을 할당한다. 해당 인덱스에서 배열의 값을 설정한다. 내장 메서드 toArray 메서드는 테이블의 컬럼 데이터를 배열 형식으로 반환한다. 예를 들어, 배열은 다음과 같이 출력된다.

{ "TaskId" = "1", "Name" => "Buy Groceries"}

뷰는 TaskId 필드를 고려하지 않는다.

{task[Name]}

위는 페이지에 작업 이름을 표시하기 위해 Name 필드를 사용하는 것을 의미한다.

이 페이지에서 처리할 폼이 없으므로 handleEvents() 메서드를 작성할 필요도 없다.

작업 목록에 사용할 초기 페이지를 작성해보자. 페이지가 로드될 때 처리된다고 가정하자. 저장한 작업목록이 없다면 $arTasks로 비어있을 것이다. Flexy 위치지정자 arTasks가 null이면 다음 블록도 아무것도 표시하지 않는다.

mmlt;li flexy:foreach="arTasks,key,task"mmgt;{task[Name]} - mmlt;immgt;{task[Due]}mmlt;/immgt;mmlt;/limmgt;

Todo/Entry 페이지로 이동해서 첫번째 작업을 만든다. flexy:foreach는 루프에서 사용할 값을 지정하며, 그림3에서 볼 수 있는 것처럼
  • 는 항목을 나열한다.

    그림3
    그림3. 작업 조회

    항목을 입력하면 Flexy는 자동으로 표시해준다.(그림4)

    그림4
    그림4. 여러 항목 나열

    끝내기

    이것으로 누구나 볼 수 있는 작업 목록 응용프로그램을 만들었다. 또한, 여기에 여러분의 스타일대로 디자인을 추가하고, 마크 기능, 카테고리 그룹과 같은 기능들을 추가할 수 있을 것이다. 다행히도 HTML은 완전히 뷰 계층에만 있으며, PHP 코드가 혼합된 HTML 없이도 템플릿을 작성하고 수정할 수 있게 되었다.

    결론

    DB_DataObjects와 Flexy 같은 도구들은 PHP에서 사용할 수 있지만 이런 통일된 방법으로 조합해서 사용하는 프로젝트는 거의 없다. PHP5에서 향상된 객체지향 기능을 통해서 WASP을 사용한 3-티어 작업이 보다 쉬워졌다. 이 기사를 토대로 WASP에서 다양한 기능을 가진 동적 웹 응용프로그램을 작성하는데 도움이 되었으면 한다. WASP에서 응용프로그램을 작성하는 것에 대한 궁금한 사항은 WASP documentation을 참고하기 바란다.

    Brian Fioca는 알래스카주 앵커리지에 위치한 PangoMedia의 수석 컨설턴트이다.
  • TAG :
    댓글 입력
    자료실