오픈소스 이야기

손쉬운 PHP 확장 기능 개발

by 김병욱 posted Dec 08, 2017
PHP 확장 기능

Web 개발 인터페이스로 널리 사용되는 PHP에는 PHP고유의 기능 외에도 사용자가 기능을 추가할 수 있는 확장(Extension) 기능이 있습니다.
확장 기능을 사용하기 위해서는 리눅스 상에서는 PHP와 인터페이스되는 확장 라이브러리를 만들어야 합니다.
PHP는 C 프로그래밍 초보자라도 확장 기능을 쉽게 만들 수 있도록 Zend Platform이라는 인터페이스를 제공하고 있습니다.

그럼 간단한 'Hello World' 확장 기능을 작성해 보겠습니다.
작성된 확장 기능은 CentOS 6.x 기준입니다.

설정하기

첫 번째 단계는 소스에서 PHP를 컴파일하는 데 필요한 필수 개발 도구 (automake, autoconf 등)를 설치하는 것입니다.
쉘 상태에서 다음 명령을 실행하면됩니다. (이미 이러한 개발 도구가 설치되어 있는 경우는 이 단계는 생략해도 됩니다)

$ sed -i "s/^\exclude.*$/exclude=/g" /etc/yum.conf # allow kernel-devel package.                                                        

$ yum groupinstall -y 'Development Tools'


git 도구를 이용하여 php 소스를 다운 받습니다.

 $ git clone http://git.php.net/repository/php-src.git                                                                                                                                        


다음 단계는 설치될 바이너리 파일의 위치를 지정하고 빌드 환경 설정 및 컴파일하는 것입니다.
아래 빌드는 테스트용 설정으로 디버깅을 고려하여 대부분 기본 설정 값으로 지정하였습니다.

$ ./buildconf

$ ./configure --prefix=$HOME/php --disable-cgi --disable-all --enable-debug --enable-maintainer-zts                                             

$ make && make install


확장 기능 만들기
이제 개발 환경을 설정하여 확장 기능을 생성 할 수 있습니다. 지금 만드는 확장 기능은 다음과 같은 두 가지 기능을 제공합니다.

hello_world () - "Hello, World!"라는 문자열을 사용자에게 반환합니다.
hello (string $name, [, bool $format]) - 이름을 입력 받아 포맷에 맞게 출력합니다. (예, "Hello, Kim!")
요약하면 아래와 같은 PHP 코드로 기능을 구현해 볼 수 있습니다.

function hello_world()

{
    return "Hello, World!";
}

function hello($name, $format = true)
{
    if ($format) {
        $name = ucfirst(strtolower($name));                                                                                         
    }

    return "Hello, " . $name . "!";
}

PHP 확장 기능을 컴파일하는 데 필요한 첫 번째 파일은 'phpize'에서 사용하는 'config.m4'파일입니다.
이 파일의 용도는 확장 프로그램의 위치와 사용 방법을 정의하는 것입니다.

PHP_ARG_ENABLE(hello, whether to enable hello support,
[ --enable-hello   Enable hello support])

if test "$PHP_HELLO" = "yes"; then
    AC_DEFINE(HAVE_HELLO, 1, [Whether you have hello])                                                                              
    PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
fi 


이제 아래의 ( 'hello.c') 예제 프로그램을 사용하여 확장 기능을 구현할 수 있습니다.

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"

#define PHP_MY_EXTENSION_VERSION "1.0"
#define PHP_MY_EXTENSION_EXTNAME "hello"

PHP_FUNCTION(hello_world);
PHP_FUNCTION(hello);

extern zend_module_entry hello_module_entry;
#define phpext_my_extension_ptr &hello_module_entry

static zend_function_entry hello_functions[] = {
    PHP_FE(hello_world, NULL)
    PHP_FE(hello, NULL)
    {NULL, NULL, NULL}
};

zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_MY_EXTENSION_EXTNAME,
    hello_functions,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
#if ZEND_MODULE_API_NO >= 20010901
    PHP_MY_EXTENSION_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};

ZEND_GET_MODULE(hello)

PHP_FUNCTION(hello_world)
{
    RETURN_STRING("Hello, World!", 1);
}

PHP_FUNCTION(hello)
{
    char *name;
    size_t name_len;
    zend_bool *format = 1;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|b", &name, &name_len, &format) == FAILURE) {
        RETURN_NULL();
    }

    if (format && name_len) {
        name = estrndup(name, name_len);
        php_strtolower(name, name_len);
        *name = toupper(*name);
    }

    char *out;
    size_t out_len = spprintf(&out, 0, "Hello, %s!", name);

    RETVAL_STRINGL(out, out_len, 0);

    if (format && name_len) {
        efree(name);
    }

    return;
} 

위의 예제를 보면 제공된 기능을 설정하고 정의하는 데 많은 매크로 작업이 필요하다는 것을 알 수 있습니다.
특히 주의해서 봐야 할 부분은 'zend_parse_parameters'함수 호출입니다.
이 함수을 호출함으로써 PHP에서 넘어오는 파라미터 문자열과 선택적으로 사용할 수 있는 bool에 의해 파라미터 값을 원하는대로 처리할 수 있습니다.
즉, s|b의 의미는 첫번째 파라미터는 반드시 string 값이며 다음에 오는 파라미터는 없거나 bool 값입니다.

코딩이 완료되었으면 새로운 확장 기능을 빌드하고 테스트 할 수 있습니다.
먼저 필요한 빌드 스크립트를 만들기 위해 'phpize'를 실행해야합니다. 컴파일 된 PHP 바이너리를 설치할 전체 경로를 지정합니다.
비슷한 방식으로 'php-config'에 전체 경로를 설정하여 빌드 환경을 구성합니다.
마지막으로 make && make install을 실행합니다.

$ $HOME/php/bin/phpize
$ ./configure --with-php-config=$HOME/php/bin/php-config                                                                                                 

$ make && make install 


PHP 바이너리를 실행하여 아래와 같이 확장 기능이 제대로 동작하는지 테스트 할 수 있습니다.

$ $HOME/php/bin/php -dextension=hello.so -r "echo hello_world();"       # Hello, World!

$ $HOME/php/bin/php -dextension=hello.so -r "echo hello('PhP');"        # Hello, Php!                                                          
$ $HOME/php/bin/php -dextension=hello.so -r "echo hello('PhP', false);" # Hello, PhP!

PHP 확장 기능은 다양한 분야에서 사용이 되고 있으며 그 중 많은 비중을 차지하는 것이 데이터베이스 인터페이스입니다.
우리가 잘 알고 있는 데이터베이스로는 Oracle, MySQL, SQLite, 그리고 CUBRID가 있습니다.
Oracle과 MySQL, SQLite 데이터베이스는 PHP 인터페이스를 오래전부터 제공해 왔으며 오픈 소스 기반인 CUBRID도 PHP를 지속적으로 지원하고 있습니다.
또한 현재 PHP는 5 버전을 지나 7 버전이 계속 배포되고 있습니다. PHP 7 버전은 5버전과는 다른 zend platform을 사용하고 있습니다.
물론 PHP 5버전이 7버전에 비해 현재까지도 널리 쓰이고 있습니다만 점차 7버전 사용 비중도 높아질 것입니다.

CUBRID 데이터베이스는 PHP5 버전은 물론 최근에 PHP 7버전에 대해서도 확장 기능을 제공하고 있습니다.
오픈 소스 데이터베이스인 관계로 소스 코드를 다운 받아 확장 기능이 어떻게 구성되어 있는지 확인할 수 있으며 사용자가 원하는대로 변경도 가능합니다.

아래는 CUBRID PHP 소스 코드를 다운 받는 git command입니다.

$ git clone https://github.com/CUBRID/cubrid-php.git                                                                                                               


다운 받은 소스코드를 위의 예제와 같이 빌드해서 시험해 보면 PHP 확장 기능을 이해하고 좀더 응용하여 개발하는데 많은 도움이 될 것입니다.
TAG •