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

한빛출판네트워크

IT/모바일

PHP 데이터객체로 비즈니스 로직 단순화하기

한빛미디어

|

2006-10-20

|

by HANBIT

18,353

제공: 한빛 네트워크
저자: Darryl Patterson, 박종필 역
원문: Simplify Business Logic with PHP DataObjects

PHP5와 단순한 데이터 객체

요즘 웹 개발자들에게 있어서 데이터베이스로 작업하는 것은 흔한 일이다. 단순한 폼 처리부터 큰 규모의 웹 어플리케이션에 이르기 까지 거의 언제나 데이터베이스를 필요로 한다. 몇 개의 프로젝트 진행 후, 사실상 모든 프로젝트에 있어서 4개의 간단한 데이터베이스 작업이 여러 번 반복된다는 사실을 깨닫기 까지는 오래 걸리지 않는다. 4개의 작업은 다음과 같다:
  1. 레코드 선택(SELECT).
  2. 존재하는 레코드 갱신(UPDATE).
  3. 새로운 레코드 삽인(INSERT).
  4. 레코드 삭제(DELETE).
약간 수정된 쿼리들이 여러 곳에 필요하기 때문에, 코드 도처에 재입력하거나 복사하고 붙인 쿼리를 찾을 수 있다. 많은 사람들이 Pear::DB 또는 DBX 같은 데이터 추상 계층(data abstraction layer)을 사용한다. 데이터 추상 계층을 사용하는 것은 좋은 방법이다. 그러나 그 것의 주된 목표는 RDBMS를 투명하게 하고 데이터베이스 공급자를 아주 쉽게 교체하기 위한 것이다. 데이터베이스의 데이터를 입출력 하는 함수의 표준화된 방법을 제공하지 못하기 때문에, 데이터 추상 계층은 데이터베이스의 테이블 구조를 추상화하는 방법을 제공하지는 못한다. 이 문제를 해결해 줄 수 있는 것이 독립적인 데이터 입출력 계층(data access layer)이다.

데이터 입출력 계층은 여러 가지 방법으로 구현할 수 있다. 그러나 여기 흥미로운 데이터객체(DataObjects)라는 한 가지 방법이 있다. 데이터객체의 개념은 Data Access ObjectTransfer Objects라고 불리는 충분히 입증된 디자인패턴들에 기초 한다. 이 방법들은 아주 복잡하다. 그러나 약간의 상상력만 발휘한다면, 이 패턴들의 기본적인 개념을 사용할 수 있고 PHP5의 고유의 데이터 계층을 만들기 위한 목적으로 아주 쉽게 작업할 수 있다. 자 그러면 함께 살펴 보기로 하자.

데이터객체란 무엇인가?

방금 전 언급한 대로 데이터객체의 개념은 디자인패턴으로부터 왔다. 만약 디자인패턴에 대한 관심을 조금이라고 가지고 있다면, 그 것들이 객체지향(object orientation)에 의존한다는 사실을 알 것이다. 이 말은 곧, PHP5의 새로운 객체 모델을 광범위하게 사용한다는 것이다. 또한 이 예제를 위해 MySQL을 사용할 것이다(비록 Pear:DB 같은 것이 더 사용하기는 쉬울지도 모른다).

기본적으로, 데이터객체는 데이터베이스의 테이블을 직접 표현하기 위한 코드 클래스 이다. -- 모든 테이블을 클래스로 만들 것이다. 클래스는 테이블의 필드와 정확히 일치하는 멤버 변수(member variables)들을 가질 것이다. 게다가, 위에서 언급한 적어도 4개의 기본적인 작업을 하기 위한 메소드(method) 또는 함수를 갖는다. 사용자 정보가 들어 있는 간단한 테이블이 있다고 가정한다:
TABLE: User
userId  INT
firstName VARCHAR(30)
lastName  VARCHAR(40)
email   VARCHAR(100)
이제 테이블의 필드 이름들과 일치하는 멤버 변수를 갖는 클래스를 만들 것이다. 나는 비슷한 클래스 이름들과의 혼란을 피하기 위해 데이터객체 클래스 이름에 "DO_" 라는접두어를 사용한다; 이 방법은 PHP에서 네임스페이스를 구현하기 위해 일반적인 방법이다. 아래 코드를 보자:
 class DO_User {
  public $userId;
  public $firstName;
  public $lastName;
  public $email;
}
위 코드는 사용자 정보 테이블로부터 하나의 행을 표현하는 단순하고 작은 랩퍼(wrapper)이다. DO_User 인스턴스(instance)는 오직 한 번에 하나의 행만 가져 올 수 있다. 이 객체를 이용해서 데이터베이스로부터 어떻게 데이터를 가져 올까? 특정 사용자 정보를 데이터베이스로부터 가져오기 위해 get()라는 새로운 메소드를 추가하자. 필요한 사용자 정보를 가져오기 위해 userId(기본키: the primary key)라는 매개변수(parameter)를 제공할 것이다.
File contents of: class-DO_User.php
userId  = $row["userId"];
    $this->firstName = $row["firstName"];
    $this->lastName  = $row["lastName"];
    $this->email   = $row["email"]
  }
}
?>
이 단순한 데이터 객체를 가지고, 이제 PHP 코드를 사용해서 데이터베이스와 작업할 수 있다. 데이터베이스를 사용하기 위해 SQL을 사용할 필요가 없다. 웹 브라우저에 사용자의 정보를 가져오고 보여주기 위한 데이터객체 스크립트가 아래에 있다:
get(5);
?>

  
    User Info
  
  
    

Here is the user info:

User ID userId?>
First Name firstName?>
Last Name lastName?>
Email email?>
데이터객체를 사용함으로써 아주 단순하고 매우 깨끗한 코드를 만들 수 있다. 사용자 테이블에 단순한 쿼리를 실행하기 위한 get() 메소드는 검색을 위해 특정의 기본키(userId) 가 필요하다. 기본키를 가지고 테이블에 질의 할 때 오직 하나의 레코드만 반환 받을 수 있다는 점을 기억하자. 단순 데이터객체는 아주 잘 수행된다. 다중 행을 검색하는 방법은 잠시 후에 다룰 것이다.

데이터객체가 레코드를 검색할 때 레코드의 데이터가 데이터객체의 멤버 변수들로 복사되는 점에 주목하자. 이 것이 테이블의 열 이름(column names)들과 데이터객체 멤버 변수들의 이름을 정확히 일치시켜야 하는 이유이다.

데이터 입출력 대 데이터베이스 추상화

여기서 잠시 데이터 입출력 계층 대 데이터베이스 추상화 계층에 대해 살펴 보자. 이 두 가지는 서로 다른 것으로 설명 된다. 전체의 유연성을 위해 각각 사용하거나 두 가지를 동시에 사용할 수 있다.

데이터베이스 추상 계층은 백그라운드에서 실행되는 RDBMS에 감춰져 있다. 만약 재치 있고 주의 깊다면 SQL문을 작성할 수 있고, 하나의 데이터베이스 서버에서 다른 서버로의 변경을 함수 또는 SQL문의 수정 없이 쉽게 처리할 수 있다. Pear:DB는 이런 것을 아주 훌륭히 처리한다.

반면에 데이터 입출력 계층은 테이블 구조 아래에 숨겨져 있다. 테이블에 대해 데이터 입출력 계층으로 사용함으로써 어플리케이션의 비즈니스 계층에서 어떤 SQL도 사용하지 않고 테이블의 데이터를 다룰 수 있다. 데이터객체는 데이터 입출력 계층을 도입하기 위한 좋은 선택이다. 테이블과 직접 관계를 맺고 있어, 전체 어플리케이션에서 수정 없이 재사용 할 수 있다.

데이터객체를 사용하기 전에는, 데이터 계층은 기능에 기초해 캡슐화되고 그룹화된(encapsulated and grouped) 쿼리들로 된 클래스들의 구성으로 만들었었다; 예를 들어, 모든 쿼리들은 등록되고 관리되는 사용자 로그인과 관련 된 경우가 그렇다.. 이런 종류의 계층이 가지고 있는 문제점은 서로 다르지만 유사한 부분이 많은 쿼리가 존재한다는 점이다. 누군 어플리케이션을 사용했는지 로그 남기기 위해 사용자 레코드가 필요하다. 또한 "사용자 정보 수정 화면."을 출력하기 위해 똑 같은 쿼리를 두 개의 다른 클래스에 복사와 붙이기를 해야 한다. 이는 코드의 구조에 기인한다. 만약 사용자의 레코드가 필요한 경우 테이블 구조를 표현한 데이터객체를 사용한다면 어떤 코드에서도 고려할 필요가 없다.

데이터베이스 추상 계층과 데이터 입출력 추상 계층을 함께 사용할 수 있다. PHP 고유의 데이터베이스 함수 대신 Pear:DB를 이용한 데이터객체를 간단히 사용할 수 있다. RDBMS를 변경할 경우가 생길 경우를 대비해 대부분 Pear:DB를 사전에 사용한다. 5년 안에 RDBMS를 변경할 경우가 생길 때는 안전을 위해 Pear:DB를 사용할 것이다. 그 외의 경우에는 PHP 고유의 DB 함수들을 사용하는 것이 코딩과 실행 속도 면에서 훨씬 빠르다. Pear:DB는 다른 추상 계층의 추가로 인해 근소하기는 하지만 실행 속도가 지연되는 결과가 발생할 수 있다.

행 삽입하기

레코드를 검색하는 경우에 데이터객체를 사용하는 것은 좋은 방법이다. 그러나 다른 경우에도 사용할 수 있어야 한다. 새로운 레코드를 테이블에 추가하는 경우를 위해 데이터를 INSERT하는 새로운 메소드를 데이터객체에 추가하자. [파일 소스]
public function insert()
{
  $sql = "INSERT INTO
        User
      SET
        firstName="" . mysql_escape_string($this->firstName) . "",
        lastName=""  . mysql_escape_string($this->lastName)  . "",
        email=""   . mysql_escape_string($this->email)   . """;
  
  mysql_query($sql);
  $this->userId = mysql_insert_id();
}
위 코드가 틀리 다고 생각하는가? 삽입하는 데이터 부분이 어디에서 오는지 이상해 할 지도 모르겠다. 데이터를 호출하기 전에는 데이터 객체에 존재한다. 때문에 insert()에 메소드에 데이터를 매개변수로 전달할 필요가 없다. 코드에서 마지막에 insert_id는 반환되고 데이터객체에 저장된다. 그래서 다른 테이블에 관련된 데이터를 삽입하기 위해서 객체가 현재 사용 가능한 상태이다. 아래 이 새로운 메소드의 사용 방법을 보라.
firstName = "Jane";
$user->lastName  = "Doe";
$user->email   = "jane.doe@example.com";

$user->insert();
?>

  
    INSERT Example
  
  
    

The user was added to the User table. The userId is: userId?>

위 예에서는 새로운 레코드를 User 테이블에 삽입하고 있다. 데이터객체를 생성하고, 필드에 변수를 할당한 후에 insert() 메소드를 호출한다. 아주 쉽다. 또한 레코드를 복사하기 위해 get()과 insert() 메소드를 함께 사용할 수 있다. 그 것은 전부가 아니며; 레코드를 복사하기 위해 약간의 수정을 해야 한다. 이메일 주소를 수정할 경우, 어떻게 하는지 아래를 보자:
get(5);

// Change the email address in the DataObject
// NOTE: This doesn"t affect the DB at all, 
// just the value in the DataObject
$user->email = "jdoe@example.com";

// Next, we call insert() to create a NEW record in the User table
$user->insert();

?>

  
    Copy Row INSERT Example
  
  
    

The user was copied to the User table. The userId is: userId?>

단지 이 것으로만 된다니, 흥분된다! 데이터객체를 사용하여 데이터베이스 관련 작업을 아주 쉽게 처리하기를 바란다.

행 갱신하기

몇 개의 값을 레코드에서 검색하고, 새로운 값을 삽입하는 것은 중요한 일이다. 보통 레코드를 변경하기 위하여 갱신 작업을 하게 된다. 이제 데이터객체에 update() 메소드를 추가해 보자: [파일 소스]
public function update()
{
  $sql = "UPDATE
        User
      SET
        firstName="" . mysql_escape_string($this->firstName) . "",
        lastName=""  . mysql_escape_string($this->lastName)  . "",
        email=""   . mysql_escape_string($this->email)   . ""
      WHERE
        userId="   . mysql_escape_string($this->userId);
  
  mysql_query($sql);
}
위 메소드는 insert() 만큼 유용하다. 이 시점 전에, 다시 한 번 데이터객체에서 필요한 데이터를 구하는 것에서 작업을 시작해야 한다. 이 메소드는 단순히 존재하는 데이터를 수정하기 위해 사용된다. 사용법은 아래를 참고하자:
get(5);

// Change the email address
$user->email = "janedoe@example.com";

// Perform the update
$user->update();

?>

  
    UPDATE Example
  
  
    

The user updated. The userId is: userId?>

처음 데이터객체를 보면, 값을 찾을 수가 없다. 데이터객체 코딩을 보면 몇 개의 반환 값이 있는 것처럼 보일 것이다. 그 값을 실제 보여주기 위하여 실제 사용하기 전까지는 의미가 없다. 예제의 마지막 보다 더 쉽게 데이터베이스에 이메일 주소를 수정할 있을까? 아주 간단한 코드처럼 보일 것이다. 그러나, 위 코드에서 보여주는 두 가지 방법은 매우 가독성이 높고 깨끗하다.

행 삭제하기

세 번째 다룰 내용은 데이터베이스의 기본적인 작업 중 4번째 이다. 데이터객체의 이점은 이 점에서 명백하다. delete() 메소드를 추가하여 삭제에 관해 검토해 보자: [파일 소스]
public function delete()
{
  $sql = "DELETE FROM
        User
      WHERE
        userId=" . mysql_escape_string($this->userId);
  
  mysql_query($sql);
}
delete() 메소드의 사용 방법은 update()와 동일하다. 삭제($user->get(5))할 행을 찾고, delete($user->delete()) 메소드를 호출한다. 다시 한 번 말하지만, 삭제할 행의 기본키가 데이터객체에 존재하여야 한다.

결과 집합

이제 데이터객체 DO_User을 사용하여, 행을 찾고 삽입, 삭제 또는 갱신을 할 수 있다. 데이터객체는 테이블에서 단일 행을 나타내는 단순한 랩퍼라고 언급한 것을 기억하는가? 여러 행을 반환하는 쿼리를 실행하기 위해서 무엇을 해야 하는가? 지금까지 소개한 데이터객체로는 이 작업을 할 수 없다. 결과 집합을 얻기 위해서는 또 다른 랩퍼가 필요하다.

이 문제를 해결하기 위한 가장 단순한 접근 방법은 검색한 모든 행을 포함하는 데이터객체 배열을 만드는 것이다. 처음에는 이 방법이 논리적인 것 같아 보인다. 그러나 쿼리의 실행으로 반환되는 행이 10,000, 100,000, 또는 그 이상일 경우에는 어떻게 할 것인가? 그렇다면 매우 큰 객체 배열을 사용해야 할 것이다. 이로 인해 어플리케이션의 메모리 사용량이 늘어나고, 웹 사이트가 다운될 시기에 도달하게 된다.

고유(또는 Pear::DB)의 결과 집합을 위해 적당한 랩퍼가 필요하다. 이 랩퍼는 각 행의 배열을 반환하지 않을 것이다. 대신, DO_User 데이터객체의 인스턴스를 반환한다. 근사하지 않은가!

이를 위해 몇 개의 새로운 클래스를 만들어야 한다. 읽기만 가능(직접 결과 집합에 행을 수정하거나 삭제할 수 없다)하기 때문에 항상 ReadOnlyResultSet이라고 호칭한다. 그리고 J2EE 디자인패턴에서 유래한 이름이기도 하다(일관성에 박수를 보내자). 이 클래스를 만들기 전에, 현재 데이터객체에서 사용하는 데이터의 행의 묶음을 찾기 위해서 DO_User에 하나 이상의 메소드를 추가해야 한다. 행을 찾을 때, 새로운 함수는 새로운 ReadOnlyResultSet 클래스의 새로운 인스턴스를 반환하게 된다. 이 것은 소리(ReadOnlyResultSet를 읽는…) 보다 쉽다. Find() 메소드를 추가할 것이다. [파일 소스]
public function find()
{
  $sql = "SELECT * FROM User";
  
  // This array will hold the where clause
  $where = array();
  
  // Using PHP 5"s handy new reflection API
  $class = new ReflectionClass("DO_User");

  // Get all of DO_User"s variable (or property) names
  $properties = $class->getProperties();
  
  // Loop through the properties
  for ($i = 0; $i < count($properties); $i++) {
    $name = $properties[$i]->getName();
    if ($this->$name != "") {
      // Add this to the where clause
      $where[] = "`" . $name . "`="" 
      . mysql_escape_string($this->$name) . """;
    }
  }
  
  // If we have a where clause, build it
  if (count($where) > 0){
    $sql .= " WHERE " . implode(" AND ", $where);
  }
    
  $rs = mysql_query($sql);
  include_once("class-ReadOnlyResultSet.php");
  return new ReadOnlyResultSet($rs);
}
이 것 중의 한 가지는 걸작이다. PHP5에는 클래스 및/또는 객체에 관한 특정한 혹은 모든 정보를 검색할 수 있는 내장된 Reflection API가 존재한다. 여기서 데이터객체로부터 기인된 완전한 속성(property) 이름(멤버 변수 이름)을 반영해 사용한다. 때때로 이 새로운 API를 살펴 보라. 매우 유용하다.

find() 메소드를 호출하기 전에, 검색하고자 하는 속성들을 데이터에 입력해야 한다. 만약 첫 번째 이름으로 어떤 사람을 검색하고자 할 때 속성을 설정하고 find() 메소드를 호출해야 한다. find() 메소드는 쿼리와 ReadOnlyResultSet의 반환 값들로 구성된다. 이 메소드 사용법을 알아 보기 전에, ReadOnlyResultSet 클래스를 코딩 해 보자.

ReadOnlyResultSet 코딩

ReadOnlyResultSet 클래스는 데이터객체 인스턴스가 반환하는 결과 집합을 위한 랩퍼이다.나는 일반적인 ReadOnlyResultSet를 모든 프로젝트를 위해 사용한다. 그 것은 수정할 필요 없이 아주 잘 작동한다. 이 새로운 랩퍼는 getNext(), rowCount(), 그리고 reset()과 같은 세 개의 메소드로 구성된다. 클래스를 위한 코드를 살펴보자: [파일 소스]
File contents of: class-ReadOnlyResultSet.php
rs = $rs;
  }

	// Receives an instance of the DataObject we"re working on
  function getNext($dataobject)
  {
    $row    = mysql_fetch_array($this->rs);

    // Use reflection to fetch the DO"s field names
    $class    = new ReflectionObject($dataobject);
    $properties = $class->getProperties();

    // Loop through the properties to set them from the current row
    for ($i = 0; $i < count($properties); $i++) {
      $prop_name        = $properties[$i]->getName();
      $dataobject->$prop_name = $row[$prop_name];
    }
    
    return $dataobject;
  }

  // Move the pointer back to the beginning of the result set
  function reset()
  {
    mysql_data_seek($this->rs, 0);
  }

  // Return the number of rows in the result set
  function rowCount()
  {
    return mysql_num_rows($this->rs);
  }
}
?>
주석에서처럼 참조 변수 전달을 위해 &를 사용하곤 한다. PHP5에서는 Java와 같이 참조에 의해 객체를 전달한다. 그 점이 위 예에서 &를 사용하지 않은 이유이다.

결과 집합을 위한 랩퍼이기 때문에, 이 클래스를 위한 생성자는 결과 집합을 전달하는 것이 필요하다. 이 예제처럼, 그 것은 MySQL 고유의 결과 집합이다, 미리 정해졌다는 이유 때문이다. reset() 메소드는 고유 결과 집합의 포인터가 처음에 0으로 되돌리게 움직인다. rowCount() 메소드는 고유 결과 집합의 행의 수를 반환한다. getNext() 메소드는 좀더 호기심을 돋운다. 좀더 자세히 분석해 보자.

첫 번째, 이 클래스는 모든 데이터객체와 작업할 필요가 있기 때문에, 이 메소드가 데이터객체와 통신할 방법이 있어야 한다. 그 이유 때문에 데이터의 다음 행을 채우기 위하여 데이터객체를 전달해야 한다. 보통 이 것은 빈 데이터객체일 것이다. 메소드에서 첫 번째 줄은 mysql_fetch_array()를 이용해 고유 결과 집합으로부터 다음 행을 가져 온다. 다음, Reflection API를 사용해 전달된 모든 객체의 속성을 반복시킬 필요가 있다. ReflectionClass 대신 ReflectionObject 클래스를 사용한다는 점에 주의하자. 이 것은 클래스가 아닌 객체에서 역공학을 시도하기 때문이다.

반복문을 실행 시킬 때, 고유 행의 각 필드 값에서 데이터객체의 각 속성 값으로 할당한다. 모든 작업이 끝나면, 할당된 데이터객체를 반환 받는다. 이 클래스는 모든 데이터객체와 작동 하고 이 클래스를 수정할 필요가 없다.

이제 ReadOnlyResultSet이 실제 어떻게 작동하는 지 보자. 여기 모든 사용자 중에서 어떻게 첫 번째 이름이 jane인 사람 찾는 예가 있다:
firstName = "jane";

// Call find(), which returns an instance of ReadOnlyResultSet
$rs        = $user->find();
?>


  
    Found Users
  
  
    

Here the found users:

?> // Loop through the result set for ($i=0; $i < $rs-rowCount(); $i++) { // Pass on a new instance of DO_User, receiving it back filled in $userRow = $rs->getNext(new DO_User()); ?> // Display the current row in an HTML table $rs->getNext()를 호출할 때 새로운 DO_User 인스턴스를 전달해야 하는 점에 주목하라. 이 점은 ReadOnlyResultSet가 어떤 종류의 데이터객체와 작업해야 하는지를 안다는 것이다. ReadOnlyResultSet는 다음 행 데이터의 새로운 데이터객체에 존재하고, 현재 존재하는 데이터 객체를 반환한다. 기억하라, PHP5는 항상 참조에 의해 객체를 전달한다. 그래서 getNext()에게 전달된 새로운 데이터 객체는 getNext()로부터 반환된 객체는 완전히 동일하다. 다시 한 번, 유용하고, 가독성 높고 깨끗한 코드를 작성해 보았다.

앞으로의 과제

소개한 다섯 개의 기본적인 메소드를 능가하는 보다 많은 함수들을 추가할 수 있다. 나는 종종 사용자 로그인 인증 메소드를 추가한다. 이 메소드는 사용자 이름과 비밀번호를 매개변수로 받아 true 혹은 false를 반환한다. 만약 true가 반환되면, 검색된 레코드를 데이터객체에 채운다. 만약 매우 특수하거나 보다 복잡한 검색이 필요하다면, 이 작업을 완수할 새로운 메소드를 추가한다. 본문에서 보여준 기본적인 메소드들은 데이터객체의 시발점일 뿐이다.

이 다섯 개의 메소드를 일반화하여 슈퍼 클래스로 만드는 것도 가능하다. Reflection API로 이 것을 쉽게 할 수 있다.

또한 이 번에 소개한 단순한 데이터객체 구현은 한 가지 제한이 있다는 점에 유의하자. 테이블 조인(join)을 구현하기가 쉽지 않다는 점이다. 조인이 필요할 때는 ReadOnlyResultSet 를 반환하는 데이터객체 뷰를 생성하면 된다. 비록 다른 방법으로 조인 문제를 해결 하여도, 데이터 삽입, 삭제, 수정 작업은 불가능하다.

마지막으로, Pear::DB_DataObject 패키지를 사용할 수 있다는 점에 주목할 필요가 있다. 이 것은 견고한 패키지 이다, 그러나 데이터객체 개념에 익숙하지 않다면 다소 이해하기 어려울 수 있다. 이 기사는 Pear::DB_DataObject에 대한 좋은 소개서이다. 다음 번에 이 패키지에 대한 글을 쓰게 되길 희망한다.

이 기사는 첫 번째 목표는 데이터객체를 살펴 보는 것이다. PHP5를 사용하여 단순하게 구현된 데이터객체를 보여준다. 약간의 수정을 하여, PHP4 뿐만 아니라, 원하는 어떤 데이터베이스 추상 계층(가령, Pear::DB 또는 DBX)에도 에서도 이 구현을 적용할 수 있다. 많은 사람들이 데이터베이스 테이블의 구조 제공하는 쿼리와 필요한 PHP 파일들을 생성에 의해 데이터객체를 자동으로 초기화 하는 스크립트를 작성하여 게시하고 있다. 이 것은 시간을 많이 단축해 준다.

다음 번 기사에서는 Pear::DB_DataObject와 PHP 5의 새로운 Reflection API를 다뤄 보기를 기대한다.

관련 자료 Darryl Patterson은 토론토 Centennial College의 수석 강사이며, PHP, SQL, Java/J2EE, HTML, JavaScript 그리고 CGI/Perl 등의 많은 프로그래밍 강의화 개발한 경험이 있다.

박종필님은 현재 ㈜케이티지에 근무하고 있으며, 에브리마켓 쇼핑몰의 시스템 개발팀 팀장을 맡고 있다. 이전에 다양한 플랫폼에서 다수의 웹사이트 개발 경험이 있으며, Database 분야에 관심이 많고 최근에는 Web2.0의 화두인 AJAX에 심취해 있다.
TAG :
댓글 입력

최근 본 상품0

User ID First Name Last Name Email
userId?> firstName?> lastName?> email?>