HipHop은 컴파일 이전에 이 PHP가 제대로 동작할지를 미리 확인해볼 수 있는 hphpi 를 제공한다.
$HPHP_HOME/src/hphpi/hphpi -m server -p 8080
현재 디렉토리를 기준으로 서버가 동작한다. 매번 빌드하는 것도 시간이 걸리는 일이므로, 이걸 쓰면 지금 작성중인 코드가 HipHop에서 잘 동작하는지 확인해 볼 수 있다.
접속해보면 잘 처리되는데...
이 PHP 파일에 include, 또는 include_once 를 써서 다른 PHP 유니트를 포함시키면 이미지 출력이 제대로 되지 않는다.
이미지 출력을 위해 사용되는 header(), imagepng() 등의 함수가 아예 동작하지 않는 듯 하다.
현 시점에서 GD 관련해서는 단일 유니트만 사용 가능할 듯. 진심으로 아쉬운 부분...
예전 게임서버는 DB 입출력을 담당하는 에이전트 서버를 통해 각종 정보를 저장해 왔었다.
이 경우 운영도 귀찮고 뭐 하나 바꾸려 할때 만져야 할 부분이 참 많다.
때문에 로그 저장 서버는 상대적으로 편한 PHP + xmlrpc 를 써서 만들었었고
나중에 게임서버를 새로 만든다면 DB 입출력은 이 방식으로 하리라 마음먹었었는데...
HipHop을 이용하면 그 부분도 네이티브 바이너리로 굴릴 수 있다는 이야기.
퍼블리셔에 텍스트로 된 PHP 파일을 그대로 전달해야 하는 찝찝함도 더불어 해결.
다만 생성된 실행파일의 크기가 약 20메가 정도로 큰 편인데, UPX로 압축하면 7메가 정도로 줄어든다.
PHP로 웹 서비스를 구현하다 보면 반복적인 작업에다 비효율적으로 파일들을 만드는 경우가 많죠. 이를 깔끔하게 해결할 수 있도록 해 주는 DAO클래스를 자동으로 생성해조는 오픈소스가 있네요.
현업에 얼마나 사용하실지는 모르겠으나 잘 응용하면 좋은 프레임워크로 만들 수 있을 것 같아 자료를 공유합니다.
개발시 디버깅을 위해 필수적으로 필요한 것이 로그이다. 이는 개발시 뿐만 아니라 운영중에 오류가 발생했을 때도 오류의 원인을 찾는데 커다란 역할을 하는 중요한 개발 요소 중의 하나이다.
PHP에서 개발자의 로그를 어떻게 남기는지 몰라서 계속해서 var_dump($variable);로 브라우저 화면에 로그를 남겨서 본 뒤에 그 로그 출력 코드를 삭제해 버리는 방식으로 작업을 했다.
대다수의 PHP 개발자들이 var_dump(), print_r() 함수등을 이용해서 위와 같은 방식으로 로그를 기록해서 디버깅을 하는 것 같다. 하지만 이는 매우 좋지 못한 방식이다.
* 로그를 출력 한 뒤에 로그 출력 코드를 항상 지워야만 한다. 그렇지 않으면 로그가 브라우저를 통해 사용자에게 전달 돼 버리므로.
* 이로인해 매우 개발이 귀찮기 짝이 없는 작업이 된다.
* 실제 운영 시간에 발생한 오류에 대해 에러 메시지만을 얻을 수 있을 뿐 그 당시 상태에 관한 로그를 전혀 얻을 수가 없다.
그러다가, 너무 짜증이나서 간단한 로깅 함수를 만들어서 바꾸었다.
그러나 여전히 맘에 안든다. 로그 레벨 지정이나, 로그 파일 날짜별로 돌리기 등 기능이 너무 부족하다.
PHP에서 로깅 전략에 대한 글을 찾아보니 우리나라에서는 거의 없는거 같고, devshed에 Logging With PHP라는 좋은 글이 있었다.
그 중에서도 PEAR Log Package를 이용하기로 하였다. PEAR에는 여러 좋은 패키지들이 많이 깔끔하게 정리되어 있는 것 같다. 근데, 우리나라 PHP 개발자들은 아직 잘 안쓰는건가? 관련 글을 찾아보기가 쉽지 않네.
그 외에도 Log4PHP가 있는데, 사실 이걸 쓰고 싶은 생각이 정말 많이 들었었다. Apache에서 만든 것이고, 이미 내게 익숙한 Log4J의 PHP 포팅이기 때문이다. 게다가 설정 파일을 통해 언제든지 로깅 방식을 변경할 수도 있다(Log4J처럼). 하지만, 참은 이유는.... 2003년 이후 버전업이 전혀 안되고 있기 때문이다. 4년씩이나 버전 업이 안되는 건 좀 이해가 안간다.
PEAR::Log 사용법과 로그 레벨 지정, 날짜별 파일로 로그 남기기, 로그를 남길 때마다 어떤 PHP파일의 몇 번째 줄에서 로그를 남겼는지 출력하기, PHP의 오류 메시지를 PEAR::Log를 이용해 로그로 남기기 등의 기법을 정리해 본다. 대부분의 내용은 PEAR::Log의 매뉴얼을 정리한 것이다.
설치
$ pear install log
로깅 객체 얻기
require_once 'Log.php';
$logger = &Log::singletone('file', 'C:/temp/test.log', 'Test'); # 로깅을 위한 객체 얻기
$logger->log('이래 저래 에러가 났잖아욧!'); # 로그 메시지 출력하기
싱글턴으로 객체를 가져옴으로써 불필요한 객체 생성과, 객체가 소유하고 있는 리소스의 중복 정의를 방지한다.
* $handler : 로그 핸들러 타입. console, file 등 로그 출력을 처리하는 방식을 지정한다.$name : 파일 이름과 같이, 로그를 기록할 자원을 지정한다. 핸들러 구현체에 따라서 이 파라미터가 뭘로 사용될지 결정된다.
* $name : 로그 파일명 등을 의미한다. $handler에 따라 의미가 달라진다.
* $ident : 로그 구분자이다. Java의 Log4j에서 클래스 FQCN과 같은 역할을 한다.
* $conf : 연관 배열로 핸들러에 사용자 정의 설정 정보를 넘겨준다.
* $maxLevel : 최대 로그 레벨을 결정한다. 기본값은 PEAR_LOG_DEBUG(최대값)이다. 이 레벨보다 낮아야 로그가 기록된다.
$logger->log("로그메시지", PEAR_LOG_NOTICE);와 같은 방식으로 로그를 기록한다.
로그 메시지 부분에는 문자열 뿐만 아니라, 일반 객체를 지정해도 된다. 객체가 getString(), toString() 혹은 __toString() 메소드를 구현하고 있다면 그 메소드를 실행해서 메시지를 기록한다. 모두 없다면 객체를 직렬화한 값을 출력할 것이다.
만약 배열과 같이 복잡한 타입의 변수 값을 제대로 찍고 싶다면 $logger->log($variable) 처럼, 문자열로 바꾸지 말고, 변수를 그대로 인자로 넘겨주면, var_dump($variable) 한 것처럼 변수 내용을 찍어준다.
로그 레벨
로그 레벨 옆의 메소드명은 log() 메소드에 로그 레벨을 지정하는 대신, 해당 메소드를 직접 호출하면, 그 로그 레벨로 로그가 남게 됨을 의미한다.
즉, $logger->log("로그메시지", PEAR_LOG_DEBUG) 는 $logger->debug(" 로그메시지") 와 동일한 역할을 한다.
* PEAR_LOG_EMERG emerg() 시스템이 사용 불가 상태에 빠졌다.
* PEAR_LOG_ALERT alert() 즉시 처리가 필요하다.
* PEAR_LOG_CRIT crit() 심각한 상태이다.
* PEAR_LOG_ERR err() 오류
* PEAR_LOG_WARNING warning() 경고
* PEAR_LOG_NOTICE notice() 주의
* PEAR_LOG_INFO info() 정보
* PEAR_LOG_DEBUG debug() 디버그 메시지
PEAR_LOG_NOTICE와 PEAR_LOG_DEBUG 레벨의 메시지만을 로그로 남긴다.
* PEAR_LOG_ALL 모든 로그 레벨을 포함하고 있는 마스크
* PEAR_LOG_NONE 어떤 로그 레벨도 포함하지 않고 있는 마스크
$mask =PEAR_LOG_ALL ^ Log::MASK(PEAR_LOG_NOTICE);
PEAR_LOG_NOTICE만 제외하고 로그를 남긴다.
$logger->getMask();
현재 로거의 로그 마스크를 가져온다.
로그 이벤트 내보내기
몇몇 로그 핸들러는 로그 메시지를 버퍼링 한다. 버퍼링된 로그 메시지를 모두 출력하도록 하려면,
$logger->flush();
혹은,
$logger->close();
로거를 닫아도 flush가 된다.
로그 핸들러
로그 핸들러는 로그를 어떤식으로 처리할지를 결정하는 것이다. 모든 로거는 로그 핸들러를 지정해야 한다.
로그 객체를가져올 때 'console' 하는 식으로 지정하는 것이다. 구체적인 사용법은 매뉴얼을 참조한다.
* console : 화면상에 로그를 출력한다.
* display : 웹 브라우저에 로그를 출력한다.
* error_log : PHP의 error_log 함수를 사용해서 로그를 출력한다.
* file : 파일로 로그를 출력한다.
* mail : 이메일로 로그를 전송한다.
* null : 로그를 무시한다.
* sql : DB에 로그를 저장한다.
* sqlite : SQLite DB에 로그를 저장한다.
* syslog : PHP의 syslog() 함수를 이용해서 로그를 저장한다.
* win : 브라우저의 새창으로 로그를 출력한다.
* composite : 여러 핸들러를 함께 사용할 수 있게 해준다.
file 로그 핸들러를 제일 많이 사용할 것이기 때문에, 이에대해 정리한다.
* 설정값 : 설정키(Type, 기본값)
o append(boolean, true) : true이면 기존의 로그파일에 계속 추가해서 쓰고, false이면 기존 파일을 삭제하고 새로 쓴다.
o mode(integer, 0644) : Unix 계열에서 파일의 허가권 지정
o eol(string, OS default) : 줄끝 문자
o lineFormat(string ,%1$s %2$s [%3$s] %4$s]) 로그 출력 형태 지정
o timeFormat(string, %b %d %H:%M:%S) : 시간 출력 형태 지정(strftime 함수로 시간을 출력한다.)
* 설정값의 lineFormat
o %1$s : 날짜와 시간
o %2$s : $ident
o %3$s : 로그 레벨
o %4$s : 로그 메시지
o %5$s : log() 메소드를 호출한 파일명
o %6$s : log()메소드를 호출한 줄 번호
o %7$s : log()메소드를 호출한 함수명
* 예제
PHP 사용중 발생하는 예외와 오류를 PEAR::Log 패키지를 이용해 처리하도록 지정할 수 있다.
이것은 set_error_handler() 함수를 이용해서 가능하다.
function errorHandler($code, $message, $file, $line)
{
global $logger;
/* Map the PHP error to a Log priority. */
switch ($code) {
case E_WARNING:
case E_USER_WARNING:
$priority = PEAR_LOG_WARNING;
break;
case E_NOTICE:
case E_USER_NOTICE:
$priority=PEAR_LOG_NOTICE;
break;
case E_ERROR:
case E_USER_ERROR:
$priority = PEAR_LOG_ERR;
break;
default:
$priority = PEAR_LOG_INFO;
}
$logger->log($message . ' in ' . $file . ' at line ' . $line, $priority);
} // end of function
set_error_handler('errorHandler');
# test
trigger_error('This is an information log message.', E_USER_NOTICE);
나는 이렇게 사용한다
강력한 설정기능 등이 Log4php에 비해 많이 딸리는 편인데, 어쨌든, 나는 아래 처럼 사용한다.
아래에서 정의한 logger() 함수는 PEAR::Log를 이용해서, 날짜별로 로그 파일을 남기는 것이다.
그리고, 로그를 남길 때는 항상 로그를 찍은 소스 파일명과 로그를 찍은 코드의 줄 번호도 함께 출력하도록 하였다.
파일 이름은 logger.php라고 치자.
<?php
require_once 'Log.php';
// PHP 5.1.x 의 set_error_handler()로 지정된 함수 내부에서
// 클래스 정의를 가진 PHP 파일을 require/include 할 경우에
// 오류가 발생하는 문제를 해결하기 위해
// 미리 필요한 클래스를 require 해둬야 한다.
// 관련 URL : http://bugs.php.net/35634
require_once 'Log/file.php';
/**
* 사용자가 명시적으로 LOG_FILENAME 상수를 지정하지 않았다면,
* 오류를 발생시킨다.
*/
if (!defined('LOG_FILENAME')) {
trigger_error("You have to define LOG_FILENAME for logger", E_USER_ERROR);
}
/**
* 사용자가 명시적으로 LOG_LEVEL 상수를 정의하지 않았다면,
* DEBUG 로그 레벨로 강제 지정한다.
*
* 이 파일 외부에서 로그 레벨을 지정할 경우, require_once 'Log.php'; 를 실행한
* 이후에 상수에 PEAR_LOG_* 값을 줘야 함을 잊어서는 안된다.
*/
if (!defined('LOG_LEVEL')) {
define ('LOG_LEVEL', PEAR_LOG_DEBUG);
}
/**
* PHP자체에서 발생하는 로그의 레벨을 지정한다.
* errorHandler() 함수에서 이 로그레벨을 넘지 않으면, 오류를 출력하지 않도록
* 만들어져 있다.
*
* 이 파일 외부에서 로그 레벨을 지정할 경우, require_once 'Log.php'; 를 실행한
* 이후에 상수에 PEAR_LOG_* 값을 줘야 함을 잊어서는 안된다.
*/
if (!defined('PHP_LOG_LEVEL')) {
define('PHP_LOG_LEVEL', PEAR_LOG_WARNING);
}
// PHP 에러 핸들러를 PEAR::Log 를 사용하도록 변경한다.
set_error_handler('errorHandler');
/**
* 파일로 로그를 저장하는 로거 객체를 리턴한다.
* Log 클래스를 직접 호출하지 말고, 일관성 있게 get_logger() 메소드를 사용해서
* 로거 객체를 얻도록 한다.
*
* LOG_FILENAME 상수로 지정된 파일 이름에 .yyyymmdd 형태로 날짜를 붙여 로그 파일을
* 생성한다.
*
* @param string $ident 로거 구분 문자열. 일반적으로 "클래스명.Method명" 혹은 "function명"
* 처럼 지정하면 된다. 이 파라미터를 지정하지 않으면 항상 get_logger를 호출한 파일의 이름으로
* 지정된다.
* @param integer 로그 레벨을 지정한다. PEAR_LOG_XXX 형태의 상수로 정의 되어있다.
* @return Log 로거 객체
*/
function logger($ident = "GlobalLogger", $logLevel = LOG_LEVEL)
{
$today_date = date('Ymd', mktime()); // 오늘날짜 yyyymmdd 형태
/**
* PHP 기본 에러 핸들러를 변경한다.
*/
function errorHandler($code, $message, $file, $line)
{
/* Map the PHP error to a Log priority. */
switch ($code) {
case E_STRICT:
case E_NOTICE:
case E_USER_NOTICE:
$priority = PEAR_LOG_NOTICE;
break;
case E_WARNING:
case E_USER_WARNING:
$priority = PEAR_LOG_WARNING;
break;
case E_ERROR:
case E_USER_ERROR:
$priority = PEAR_LOG_ERR;
break;
default:
$priority = PEAR_LOG_WARNING;
}
$logger = logger("PHPError", PHP_LOG_LEVEL);
$logger->log($message . ' in ' . $file . ' at line ' . $line, $priority);
}
/**
* 변수를 받아서 var_export()한 결과를 문자열로 저장하여 넘겨준다.
* 로그 메시지로 복잡한 형태의 변수나 배열을 출력하고 싶을 때 사용한다.
*
* @param mixed $variable 출력할 변수
*/
function var_export_str($variable) {
ob_start();
var_export($variable);
$logmsg .= ob_get_contents();
ob_end_clean();
return $logmsg;
}
?>
위 logger()메소드는 다음 처럼 간단히 활용할 수 있다.
<?php
// 로그레벨 설정 상수가 Log.php에 들어 있기 때문에
// 로그 레벨 설정과 logger.php보다 Log.php를 먼저 require 해줘야 한다.
require_once 'Log.php';
PHP 3.0.13 이후의, php://outputPHP 4.3.0 이후의 php://input, PHP 5.0.0 이후의 php://filter
php://stdin
php://stdout
php://stderr
php://output
php://input
php://filter
php://stdin, php://stdoutphp://stderr은 PHP 프로세스의 입력이나 출력 스트림에 대한 접속 권한을 허용한다.
php://output은 print() 와 echo() 같은 출력 버퍼 메카니즘에 대한 쓰기 권한을 허용한다.
php://input은 raw POST 데이터를 읽을수 있는 권한을 허용한다. $HTTP_RAW_POST_DATA에 대한 메모리 집적에 대한 차선책이고, 특별한 php.ini 디렉티브 설정이 필요치 않다.
php://stdin 과 php://input 은 읽기 전용, 반면에 php://stdout, php://stderr 은 php://output 쓰기 전용이다.
php://filter는 스트림을 열때 필터의 응용을 허용하도록 설계된 메타-래퍼(meta-wrapper)의 한 종류이다. 컨텐츠를 읽기전에 스트림에 대한 필터를 적용할 기회가 전혀 없는 readfile(), file(), file_get_contents() 같은 올-인-원(all-in-one) 함수에 대한 가용성을 갖는다.
php://filter 타겟은 그 'path'의 부분으로서 다음 'parameters'를 취한다.
/resource=<필터링되는 스트림> (필수) 이 인자는 php://filter 사양의 끝부분에 위치해야 하고 필터링을 원하는 스트림을 가리켜야 한다.
<?php
/* This is equivalent to simply:
readfile("http://www.example.com");
since no filters are actually specified */
/read=<읽기 체인에 적용되는 필터 리스트> (선택적) 이 인자는 | 파이프 문자로 구분되는 하나이상의 필터명이 적용된다.
<?php
/* This will output the contents of
www.example.com entirely in uppercase */
readfile("php://filter/read=string.toupper/resource=http://www.example.com");
/* This will do the same as above
but will also ROT13 encode it */
readfile("php://filter/read=string.toupper|string.rot13/resource=http://www.example.com");
?>
/write=<쓰기 체인에 적용되는 필터 리스트> (선택적) 이 인자는 | 파이프 문자로 구분하는 하나이상의 필터명을 적용합니다.
<?php
/* This will filter the string "Hello World"
through the rot13 filter, then write to
example.txt in the current directory */
file_set_contents("php://filter/write=string.rot13/resource=example.txt","Hello World");
?>
<양쪽 체인에 적용되는 필터 리스트> (선택적) read=나 write=에 선행하지 않는 모든 필터 목록은 읽기와 쓰기 양쪽 모두의 체인에 (적절하게) 적용합니다.
표 J-6. 래퍼(Wrapper) 요약(php://filter에 대한, 필터링 되는 래퍼의 요약으로 참조됨.)
php4에서는 단일 상속과 몇가지에 대한 지원밖에는 해주지 못했지만.. php5에서는 자바의 객체지향 모델을 가지고 옴으로써 여러가지를 지원해주게 됩니다.
상속
여전히 상속은 단일 상속만을 지원합니다.
하지만 interface 개념을 도입함으로써 여러가지로 작동하는 원리로 만들어줍니다. interface는 다중상속을 위해서 나온 것은 아닙니다. 그러니 다중상속 개념은 없다고 보는것이 맞겠지요...
2. 인터페이스
php5에서는 interface 개념을 지원합니다. interface는 다른 객체와 잘 부합할 수 있도록 객체의 표준을 마련하는 것이라 보면 쉽겠습니다.
3. 추상 클래스
인터페이스와 비슷한 개념으로 추상클래스를 지원합니다. 하지만 추상클래스와 인터페이스의 용도는 좀 다르죠.. 일단 클래스이기 때문에 상속은 단일 상속밖에 할 수 없습니다. 그리고 추상메소드를 정의를 해야하고 abstract 라는 키워드를 써서 추상클래스임을 명시해야합니다. 일단 설계하는 클래스가 여러가지로 몇가지 기능을 빼고는 다 같은 방식으로 작동이 된다면 추상클래스는 아주 좋은 선택이 될 수도 있습니다.
php5에서는 추상 클래스에서 인터페이스를 구현(implements) 하면 인터페이스에 속해 있는 메소드들은 무조건 실제 작동하는 메소드로 생성이 되어야 합니다. 하지만 자바에서는 인터페이스 메소드들도 그대로 추상메소드로 되어질 수 있습니다. 이것이 조금 번거로울 수도 있겠네요.
예를 들어서 java 는
interface A { public void a();}
abstract class B implements A { abstract public void a(); }
php는
interface A { public function a(); }
abstract class B implements A { public function a() { echo "aaa"; } }
위의 예제와 같이 java는 interface의 메소드(추상메소드이지요)는 추상클래스내에서 다시 추상메소드가 될 수 있지만 php는 반드시 구현되어야 하는 메소드가 되버립니다.
4. 오버로딩(Overloading), 오버라이딩 (Overriding)
오버로딩은 같은 클래스 내에서 이미 정의해 놓은 메소드를 같은 의미이지만 다른 매개변수를 사용 할 때 같은 이름의 메소드를 정의 할 수있게 해주는 것을 말합니다.
오버라이딩은 상속관계에서 같은 메소드이지만 다른 내용으로 만들고 싶을 때 메소드를 재정의 하는 것을 말합니다.
php5 에서 사용하는 방법은 조금 신기합니다. ㅋㅋ
일단 오버라이딩은 자동으로 됩니다... php에서는 같은 이름의 함수가 있으면 가장 마지막에 선언이 되어진 함수로 실행을 하기 때문에 오버라이딩은 자동으로 되게 되어있습니다. 그렇다면 오버로딩은 어떨까요?
php5 에서는 몇가지 매직함수를 지원하는데요.. 그중에서 __call 이라는 놈이 재미난 놈입니다...
이놈은 선언 되어지지 않은 메소드가 실행이 될때 실행되어지는 메소드 입니다.
예를 들어서
class AAA {
function run() {
echo "뛰어";
}
}
$a = new AAA();
$a->run();
일반적으로는 이렇게 실행을 하는데요....
$a->run_to("진호");
이런식으로 선언되어지지 않은 메소드를 사용하면 __call 이 자동으로 불리어집니다. ㅋㅋ 오~~ 여기서 부터 재밌습니다.
이런식으로 __call 은 메소드 이름과 매개변수 리스트를 배열로 받아들인다. ㅋㅋ 자 이간단한 개념을 가지고 살짝 재밌는 기능을 구상해 보자.
요즘 웹프레임워크 중에 제일 잘 나가고 있는 루비의 일부 기능을 흉내내보겠당... 루비는 메타프로그래밍이 가능한 아주 유연한 언어이다.
실행중에 클래스 메소드 자체를 마음대로 바꾸어 줄 수 있다. 그런 기능을 이용해서 레일스에서는 액티브 레코드에 여러가지 기능을 넣어두었다.
예를 들어
obj.find_by_name("진호") --- name 필드가 진호인 것을 찾는다.
여기서 주의깊게 봐야할 부분이 name 부분인데 이것은 필드 이름인데 언제 어디서든 바뀔 수가 있답니다. 그렇다면 메소드가 동적으로바뀐다는 말이되겠군요..
어! 메소드가 동적으로 바뀌네 하면 php에서는 __call 을 떠올리세요. ㅋㅋ
function __call($func, $args) {
if (strpos($func, "find_by_") == 0) {
// 자 이제 메소드에서 나머지 부분만 때어 볼까요?
$arr = explode("find_by_", $func);
$field = $arr[1]; // 이렇게 하면 필드 이름이 나오겠네요.. 오호~~
$value = $args[0]; // 이렇게 하면 검색 하는 값이 나오겠네요. .오.. 정말..
// 오 쉽게 select 구문이 되네요.. ㅋ
$sql = "select * from 테이블 where {$field} = '{$value}' ";
// 나머지는 상상에 맡길게요.. ㅋ
}
}
제가 여기서 말하고자 하는 것은 php5 오면서 상당히 유연해졌다는 것입니다. 그리고 pecl에 있는 라이브러리 중에서는 실행시간 중에도 php의 메소드를 바꿔주는 메소드도 있습니다.. 즉 메타프로그래밍이 된다는 거죠.... ^^
앞으로도 php는 발전 가능성이 너무 많네요.... ㅋ
5. 다형성
다형성이라는 말은 여러가지로 해석이 될 수 있는데요,,, 하나의 자료형에 여러가지 다른 자료형을 넣을 수 있느냐는 것과 서로 유사한 클래스들을 하나의 인터페이스로서 제어 할 수 있느냐는 것도 다형성 중에 하나의 요소가 됩니다. 이것에 대해서는 php는 왕이죠.. 거의.. (필자가 생각하기에는.. ㅋㅋ )
array 하나로 모든 것을 저장하고 내 뱉고 검색 할 수 있답니다.. 심지어는 array_multi_sort 라는 함수를 이용하면 db 에 있는 데이타를 가지고 와서 배열에 담아두고 그 자체로 다시 정렬 할 수 있습니다. 그래서 php는 기본적으로 다형성을 가지고 시작을 합니다. 모든 자료형은 동적으로 생성이 됩니다. 스크립트 언어들의 거의 기본적인 특징이죠... 그래서 좀 더 유연하고 쉽고 빠르게 프로그래밍을 배울 수 있답니다. 하지만 쉽다는 것이 곧 아무나 한다는 의미는 아니니 명심하세요...
6. 동적 바인딩
동적 바인딩은 실행시간에 실제 그 객체의 메소드를 정확하게 찾아서 메소드를 실행 시키는 방법입니다. 이 말은 C++이나 자바에서 많이 접할 수 있는데요...
C++이나 자바는 미리 클래스를 선언해주고 변수에 객체를 할당합니다. 그래서 그 클래스 변수에는 딱 정해진 자료형으로만 사용되어지는데요...
그래서 인터페이스나 추상클래스를 두고 실제 움직이는 메소드에 대해서는 다른 클래스에 맡겨 두는 방식이 됩니다. 그렇게 되면 여러 클래스들이 인터페이스나 추상클래스 앞으로 모일 수가 있답니다. 즉 인터페이스와 추상클래스를 가지고 다른 클래스들을 공통적인 방법으로 실행하지만 실제 실행되는건 인터페이스나 추상클래스가 아닌 실제 그것을 구현한 클래스에 들어있는 메소드라는 것입니다.. 자기 자신을 아는 것이죠...
하지만 php는 애초에 자료형을 지정해줄 필요가 없답니다. 그래서 지금 내가 할당되어진 객체 그 자체로 실행을 하기 때문에 언제든 동적 바인딩이 된다고 볼수가 있답니다. 그러니 좀 더 편하게 프로그래밍을 할 수 있답니다.
이 인터페이스를 구현하면 foreach문에 객체를 집어넣어서 배열처럼 돌릴 수 있다. Traversable은 유사 인터페이스고, 실제로는 Iterator 혹은 IteratorAggregate를 구현하면 된다. 전자는 반복자를 직접 구현하는 방식이고, 후자는 IteratorAggregate->getIterator() 메서드를 구현하여 반복자 객체를 위임하는 방식이다. 간단하게 배열처럼만 작동하는 객체를 의도한다면 후자가 좋고, 좀더 정교한 것을 원하면 직접 Iterator를 구현하는 것이 좋다.
Countable
이 인터페이스를 구현하면 count() 함수에 넣었을 때 배열처럼 원소 개수를 반환하게 할 수 있다.
ArrayAccess
정말 유용하다. 이거 모르는 사람이 매우 많은데, 첨자 연산자([])를 오버로드할 수 있다. 단순히 값을 반환하는 것 말고도 해당 키가 존재하는지 확인하는 것(isset($obj[$key])), 해당 키의 원소를 삭제하는 것(unset($obj[$key])), 원소를 덧붙이는 것($obj[] = $value), 해당 키의 원소 값을 수정하거나 추가하는 것($obj[$key] = $value)이 가능하다.
아, 중요한 것 하나. 배열과 달리 ArrayAccess를 구현한 객체는 키 값으로 스칼라 외에 배열이나 객체도 받을 수 있다.
가변적인 갯수의 인자를 전달받을 때 사용한다. PHP는 함수 선언시에 명시한 시그너쳐에 맞지 않는 호출을 하면 오류를 내는데, 시그너쳐의 갯수보다 많은 인자를 받을 땐 오류를 내지 않는다. 이걸 이용하면 가변 인자를 받으면서 쉽게 오류 처리를 할 수 있다. 예를 들어 인자 갯수는 가변적이지만, 꼭 맨 처음의 인자 하나는 배열로 받아야 한다는 제약 사항이 있다면?
function get_array_and() {
$args = func_get_args();
if(!count($args) or !is_array($args[0]))
throw new InvalidArgumentException('First parameter must be array');
$array = $args[0];
위와 같이 할 수도 있지만, 아래처럼 하면 굳이 제약 사항을 검사하고 오류를 내는 코드를 직접 작성할 필요가 없다.
function get_array_and(array $array) {
$args = func_get_args();
주의할 것이 있다. 이 함수의 호출은 항상 맨 첫 문장, 대입식을 제외한 가장 바깥 표현식이어야 한다. 그렇지 않으면 의도한대로 작동하지 않는다. 이유는 나도 모른다. 하지만 이 버그는 PHP쪽에서는 유명하다.
PHP 5에서 추가된 리플렉션 기능을 사용하면 클래스의 멤버와 메서드 목록을 얻는다던가, 함수의 시그너쳐를 추적하는 등의 일을 할 수 있다. 인자에 타입 힌트가 있는지, 있다면 어떤 타입인지, 기본값이 있는지, 변수 이름이 무엇인지 등도 알 수 있다. 이런걸 이용하면 Python의 키워드 인자도 흉내낼 수 있을 것이다.
이것도 마법 메서드의 하나인데, PHP 5에서 추가된 기능이다. 이 메서드를 정의한 객체는 echo문으로 출력할 때 __toString()이 반환한 문자열을 출력한다. Python의 __str__ 속성과 비슷한 용도이다. 그런데 이게 PHP 5.2 이후로는 substr() 같이 문자열을 받는 함수에 전달될 때 자동으로 해당 문자열로 캐스팅도 된다. 물론 명시적으로 (string) 캐스팅 연산자를 쓰는 것도 잘 된다.
이 함수를 사용하면 호출 스택을 추적하는 것이 가능하다. eval()을 통하는 것과, include, require를 통한 것도 모두 추적할 수 있다. 대체 이 함수를 어디에 쓰겠느냐고 무심코 지나치면 안된다. 이 함수를 기억하고 있는 것만으로도, 종종 우연히 이 함수를 이용하여 좀더 우아한 DSEL을 달성할 때가 많다.
이 함수를 이용하는 대신 예외 객체를 생성하여 Exception->getTrace() 메서드로 받아내는 방법도 있다.
__FILE__ 상수의 값은 include, require 등과 무관하게 해당 상수는 현재 코드가 위치한 실제 파일 경로이다. 예를 들어 require_once dirname(__FILE__).'/file.php'과 같이 해당 소스 파일과 같은 위치에 있는 file.php를 불러올 수 있다. (저렇게 하지 않으면 include 호출의 맨 위쪽에 있는 파일에 상대한 경로로 인클루드를 시도한다.)
PHP에 기본적으로 포함되지는 않았지만, 매우 유용한 확장이다. 연산자 재정의를 가능하게 해준다. C++의 Boost.Spirit 라이브러리 같은 것을 보면 연산자 재정의가 얼마나 언어의 표현력을 높여주는지 알 수 있다. 자세한 것은 내가 예전에 정리해둔 문서를 참고하시라.