>  기사  >  백엔드 개발  >  PHP 확장 1 작성: PHP 및 Zend 소개

PHP 확장 1 작성: PHP 및 Zend 소개

WBOY
WBOY원래의
2016-07-30 13:29:341040검색
확장 기능 작성 1부: PHP와 Zend 소개

번역: 세상에 빠졌어요

원본 주소 : http://devzone.zend.com/303/extension-writing-part-i-introduction-to-php-and-zend/

블로그 주소: http://lg.uuhonghe . com/index/view?id=3

소개

확장이란

라이프사이클

Hello World

자신만의 확장 프로그램 만들기

ini 구성

전역 변수

ini를 전역 변수로 설정

무결성 검사

그럼?

소개

이 튜토리얼을 읽고 계시다면 PHP 언어로 확장 기능을 작성하는 데 관심이 있으실 것입니다. 그렇지 않다면. . . 아마도 이 책을 읽고 나면 이전에 알지 못했던 것에 관심이 있다는 것을 알게 될 것입니다.

이 글은 독자가 PHP 언어와 C로 작성된 PHP 인터프리터에 대한 기본적인 이해를 갖고 있다고 가정합니다.

먼저 PHP 확장을 작성하려는 이유를 확인해 보겠습니다.

1. 추상화로 인해 언어 코어 Depth를 사용하면 PHP를 사용하여 직접 수행할 수 없는 일부 라이브러리 및 시스템 호출을 사용할 수 있습니다.

2. PHP가 몇 가지 특이한 방식으로 자체 동작을 구현하기를 원합니다.

3. 많은 PHP 코드를 작성했지만 더 빠르게 실행될 수 있다는 것을 알고 있습니다.

4. 판매하고 싶은 특히 기발한 아이디어를 구현하는 코드가 있지만 더 중요한 것은 판매하려는 코드가 실행될 수 있어야 하지만 실행될 수는 없다는 것입니다. 소스 코드에서 볼 수 있습니다.

이는 모두 매우 타당한 이유이지만 확장 프로그램을 만들려면 먼저 확장 프로그램이 무엇인지 이해해야 합니다.

확장이란 무엇인가요?

PHP를 작성했다면 확장 기능을 사용했을 것입니다. 몇 가지 확장만 있으면 PHP의 모든 사용자 공간 기능은 이 확장 또는 저 확장의 기능 그룹에 있습니다. 이러한 기능 중 다수는 표준 확장의 일부이며 총 400개 이상입니다. PHP 소스 코드에는 86개의 확장 기능이 번들로 포함되어 있으며 각 확장 기능에는 평균 약 30개의 기능이 포함되어 있습니다. 계산해 보면 총 약 2500개의 기능이 있습니다. 충분하지 않다면 PECL 저장소에서 100개 이상의 추가 확장 기능을 사용할 수 있으며, 온라인의 다른 곳에서도 더 많은 확장 기능을 찾을 수 있습니다.


PHP의 핵심은 두 부분으로 구성됩니다. 하단에는 Zend Engine(줄여서 ZE)이 있습니다. ZE는 사람이 인식할 수 있는 스크립트를 기계가 인식할 수 있는 기호로 구문 분석하고 이러한 기호를 프로세스 공간에서 실행합니다. ZE는 메모리 관리, 변수 필드 및 함수 호출을 동시에 처리합니다. 이러한 차이점의 또 다른 부분은 PHP 코어입니다. PHP 커널은 통신, 연결 및 SAPI 계층(서버 응용 프로그래밍 인터페이스, Apache, IIS, CLI, CGI와 같은 호스트 환경을 참조하는 데 자주 사용됨)을 처리합니다. 등)은 파일 및 네트워크 I/O를 위한 사용자 공간 함수 fopen(), fread() 및 fwrite()와 관련된 스트리밍 계층뿐만 아니라 제어 계층에서 safe_mode 및 open_basedir의 통합 감지도 제공합니다.

라이프 사이클

SAPI가 시작될 때(예: /usr/local/apache/bin/apachectl) start

에 대한 응답으로 PHP는 커널 하위 시스템을 초기화하여 시작합니다. 이 부팅 프로세스가 끝나면 각 확장의 커널이 로드되고 해당 모듈 초기화 루틴(

MINIT)이 호출됩니다. 이는 각 확장에 내부 변수를 초기화하고, 리소스를 할당하고, 리소스 핸들을 등록하고, 해당 함수를 ZE에 등록할 수 있는 기회를 제공하므로 스크립트가 이러한 함수를 호출할 때 ZE는 어느 것을 실행할지 알 수 있습니다. 코드 조각.


다음으로, PHP는 SAPI 레이어가 처리할 페이지를 요청할 때까지 기다립니다. CGI 또는 CLI SAPI의 경우 이는 직접적으로 한 번만 발생합니다. Apache, IIS 또는 기타 성숙한 웹 서버 SAPI에서 이는 원격 사용자가 요청할 때 발생하며 동시성으로 여러 번 발생할 수 있습니다. 요청이 어떻게 도착하는지에 관계없이 PHP는 ZE에게 스크립트를 실행할 환경을 설정하라고 지시한 후 각 확장의 요청 초기화(
RINI

) 함수를 호출합니다.

RINI는 확장 프로그램을 통해 고유한 특정 환경 변수를 설정하고, 특정 리소스에 대한 요청을 할당하거나, 감사와 같은 기타 작업을 수행할 수 있는 기회를 제공합니다. RINI함수 동작의 대표적인 예는 세션입니다. 확장에서 session.auto_start 항목이 활성화되면 RINI가 자동으로 사용자 공간 session_start() 함수를 실행하고 $_SESSION변수. 요청이 초기화되면 ZE는 PHP 스크립트를 토큰으로 변환하고 마지막으로 단일 단계 디버깅 및 실행이 가능한 opcode로 변환합니다.

opcode 중 하나에 포함된 확장 메소드가 호출되면 ZE는 메소드의 매개변수를 묶고 일시적으로 직접 메소드 완성에 제어권을 넘깁니다.

스크립트가 완료된 후 PHP는 각 확장 요청 종료(RSHUTDOWN) 함수를 호출하여 최종 정리 작업(예: 세션 변수를 확인하는 등)을 수행합니다. 디스크). 다음으로, ZE는 요청의 이전 부분에서 사용된 모든 변수에 대해 unset() 작업을 효과적으로 수행하는 정리 프로세스(가비지 수집이라고 함)를 실행합니다.


작업이 완료된 후 PHP는 SAPI가 다른 문서를 요청하거나 신호가 닫힐 때까지 기다립니다. CGI, CLI SAPI의 경우 "다음 요청"이 없으므로 SAPI가 직접 종료 프로세스를 시작합니다. 종료 프로세스 동안 PHP는 각 확장을 다시 거쳐 모듈 종료(MSHUTDOWN) 함수를 호출하고 마지막으로 자체 커널 하위 시스템을 종료합니다.

위의 내용이 겁나게 들릴 수도 있지만 작동하는 확장 프로그램을 개발하기 시작하면 일부 내용이 점차 명확해집니다.

메모리 관리

잘못 작성된 확장 메모리의 손실을 방지하기 위해, ZE는 지속성을 나타내는 추가 플래그를 사용하여 내부 메모리 관리자를 실행합니다. 메모리 할당이 페이지 요청보다 오래 지속되도록 하려면 지속적인 할당이 중요합니다. 반면, 비지속적 할당은 해제 함수 호출 여부에 관계없이 할당된 요청이 끝나면 해제됩니다. 예를 들어 사용자 공간 변수는 요청이 끝난 후 더 이상 사용되지 않으면 비영구적으로 할당됩니다.

그러나 아마도 확장은 이론적으로 페이지 요청이 끝날 때 비영구 메모리를 자동으로 해제하기 위해 ZE에 의존할 것입니다. 이는 권장되지 않습니다. 메모리를 할당하면 재활용 취소 기간이 길어지고, 메모리 관련 리소스가 적시에 닫힐 가능성이 낮으며, 정리 작업이 없으면 이 작업이 엉망이 됩니다. 나중에 보게 되겠지만, 할당된 데이터가 제 시간에 정리되는지 확인하는 것은 간단한 문제입니다. 전통적인 메모리 할당(외부 라이브러리를 사용할 때 사용해야 함)과 PHP/ZE의 지속성 및 비지속적 메모리 할당을 간단히 비교해 보겠습니다.

Traditional Non-Persistent Persistent
malloc(count)
calloc(count, num)
emalloc(count)
ecalloc(count, num)
pemalloc(count, 1)*
pecalloc(count, num, 1)
strdup(str)
strndup(str, len)
estrdup(str)
estrndup(str, len)
pestrdup(str, 1)
pemalloc() & memcpy()
free(ptr) efree(ptr) pefree(ptr, 1)
realloc(ptr, newsize) erealloc(ptr, newsize) perealloc(ptr, newsize, 1)
malloc(count * num + extr)** safe_emalloc(count, num, extr) safe_pemalloc(count, num, extr)
 

* pemalloc()pemalloc() 계열 포함 비지속적 부분에 해당하도록 만드는 '지속적' 플래그입니다. 예를 들면 다음과 같습니다.

                                                                   safe_emalloc(1234)safe_pemalloc(1234, 0)safe_pemalloc(1234, 0) 마찬가지입니다.

** safe_emalloc()safe_emalloc()safe_pemalloc()safe_pemalloc()

( 존재하다 PHP5)은 정수 오버플로를 방지하기 위한 검사를 추가합니다.

개발 환경 구축


이제 PHP와 Zend 엔진의 작동 방식에 대한 몇 가지 이론을 배웠으니, 시작하고 싶어 그의 기술이 발전하기 시작했습니다. 그런 다음 시작하기 전에 요구 사항을 충족하는 개발 환경을 설정하는 데 필요한 몇 가지 작업을 수집해야 합니다.

먼저 PHP 자체가 필요합니다. 이 일련의 개발 도구는 PHP와 분리될 수 없기 때문입니다. 소스 코드를 사용하여 PHP를 구축하는 데 익숙하지 않다면 먼저 이 기사를 읽어 보시기 바랍니다: http://www.php.net/install.unix. (Windows를 사용하여 PHP 확장을 개발하는 방법에 대한 기사는 나중에 제공됩니다) . 안전하게 Linux 배포판에 포함된 바이너리 패키지를 사용하고 싶은 유혹이 있지만, 개발 중에 매우 편리한 두 가지 ./configure 옵션이 누락됩니다. 첫 번째는 --enable-debug--enable-experimental-zts입니다. --enable-maintainer-zts 이 옵션을 사용하면 PHP가 컴파일할 때 실행 가능한 기호 정보를 추가하므로 세그폴트가 발생할 때 커널 저장소에서 정보를 가져와 gdb를 사용하여 세그폴트가 발생한 위치와 이유를 추적할 수 있습니다. 또 다른 옵션은 개발 중인 PHP 버전에 따라 다릅니다. 이 옵션의 이름은 PHP 4.3에서는 , PHP 5 이상에서는 입니다. 이러한 우선 순위를 통해 PHP는 멀티 스레드 환경에서의 작업에 대해 생각할 수 있으므로 스레드가 아닌 환경에서는 무해하고 확장 기능을 멀티 스레드 환경에서 사용할 수 없게 만드는 몇 가지 일반적인 예외를 포착할 수 있습니다. 개발 서버(또는 워크스테이션)에 이러한 추가 옵션을 설치하여 PHP를 컴파일했다면 첫 번째 확장 기능 작성을 시작할 수 있습니다.


                                                                                               완료되었습니다. 따라서 아래에서는 "Hello World" 문자열을 반환하는 함수 하나만 확장해보겠습니다. PHP 코드에서는 다음과 같이 작성할 수 있습니다.


이제 PHP 확장을 사용하여 이 코드를 구현합니다. 먼저 ext/ 디렉터리에
디렉터리를 만듭니다. PHP 소스 코드 hello

를 입력하고 해당 디렉터리를 입력하세요. 사실, 이 디렉토리는 PHP 디렉토리 트리 내부 또는 외부에 있을 수 있지만 나중에 관련 없는 개념을 보여주기 위해 여기에 두도록 하겠습니다. 이 디렉터리에 세 개의 파일을 생성해야 합니다.
<?php
function hello_word(){
    return 'Hello World';
}
?>

메서드가 포함된 소스 파일, 확장 프로그램을 로드하기 위한 PHP에 대한 참조가 포함된 헤더 파일, phpize용 헤더 파일 컴파일할 확장을 준비하기 위한 구성 파일입니다. hello_world config.m4


php_hello.h

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

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

hello.c

#ifndef PHP_HELLO_H
#define PHP_HELLO_H 1

#define PHP_HELLO_WORLD_VERSION "1.0"
#define PHP_HELLO_WORLD_EXTNAME "hello"

PHP_FUNCTION(hello_world);

extern zend_module_entry hello_module_entry;
#define phpext_hello_ptr &hello_module_entry

#endif

위 확장 예제의 코드 대부분은 글루 프로토콜 언어이며, 이는 PHP에 확장을 소개하고 PHP에 대한 대화를 설정하는 데 사용됩니다. 그들이 의사소통하게 하세요. 코드의 마지막 4줄만 "실제 코드"라고 부를 수 있으며 사용자 공간 레이어 스크립트가 상호 작용할 수 있는 작업을 수행하는 데 사용됩니다. 실제로 이 수준의 코드는 이전에 살펴본 PHP 코드와 매우 유사해 보이며 이해하기 쉽습니다.

        1、声明一个方法  hello_world

        2、让这个方法返回一个字符串:"Hello World"

        3、。。呃。。。1?这个1几个意思?

    回忆一下,ZE包含了一套复杂的内存管理层,能确保分配的资源在脚本退出时被释放。然而在内存管理的掌控下,释放同样的块两次是非常大的禁忌。这种行为通常称为"double freeing",是一个常见的段错误的原因,涉及到一个正在调用的程序访问一个不再属于它的内存块。同样的,你不希望允许ZE去释放一个存活于程序空间并且其数据块被其他进程占用的静态字符串buffer(例如我们示例中的"Hello World")。  RETURN_STRING()能够假设任何一个传递给它的字符串都被复制过以便可以安全的释放;但是因为在内核函数里分配内存给一个字符串不太常见,所以动态的填充它,然后返回,  RETURN_STRING()允许用户指定是不是必须复制字符串。为了进一步说明这个概念,下面这个代码片段作用和上面相应部分一样:

PHP_FUNCTION(hello_world)
{
    char *str;
    str = estrup("hello World");
    RETURN_STRING(str, 0);
}

    在此版本中,我们手动地分配内存给"Hello World"这个串,并且最终传回调用脚本,然后把内存传给 RETURN_STRING()第二个参数值为0表示不需要复制一份,可以直接使用传递过来的。

    编译你的扩展

    本练习的最后一步就是将你的扩展编译为一个动态可加载模块。如果你把上面的示例原封不动的抄下来,那么只需要在ext/hello/目录下运行下面三步命令就行:

phpize
./configure --enable-hello

(译者注:如果编译PHP的时候使用了 --prefix 参数,此处要加上 --with-php-config 选项,
如笔者编译PHP时使用的是 ./configure --prefix=/use/local/phpdev/ 此处命令应使用
./configure --enable-hello --with-php-c/local/phpdev/bin/php-config)

make

    运行完上述三个命令之后,你应该在ext/hello/modules/下找到一个 hello.so 文件。(译者注:如果在make时报错: error: unknown type name 'function_entry' ,可以把 'function_entry' 改为 'zend_function_entry',参见:https://bugs.php.net/bug.php?id=61479  )。现在,就像其他PHP扩展一样,你可以把你的扩展拷贝到扩展目录(默认为,/usr/local/lib/php/extensions/,可以通过php.ini确认)然后在php.ini里加上extension=hello.so一行可以在以触发它在程序启动时被加载到了。对于CGI/CLI SAPIs 来说,启动就指下一次运行PHP;而对我web server SAPIs如Apache来说,启动指下次web server重启。让我们试下运行下面命令:

$ php -r 'echo hello_world();'

    如果一切顺利,你现在应该能看到这段代码输出"Hello World"了,因为你的扩展里的"hello_world()"返回了一个字符串"Hello World",而echo命令会原封不动地显示传递给他的参数(此处即为该函数的返回值)。

    其他标量也可用类似的方式返回,使用RETURN_LONG() 返回整型, RETURN_DOUBLE() 返回浮点数, RETURN_BOOL() 返回true/false值,RETURN_NULL() 你猜到了,返回 NULL 值。让我们来逐行分析一下在hello.cfunction_entry 结构体下通过 PHP_FE() 添加的行和文件末尾的那些PHP_FUNCTION()都做了些什么。

static function_entry hello_functions[] =
{
    PHP_FE(hello_world, NULL)
    PHP_FE(hello_long, NULL)
    PHP_FE(hello_double, NULL)
    PHP_FE(hello_bool, NULL)
    PHP_FE(hello_null, NULL)
    {NULL, NULL, NULL}
};

PHP_FUNCTION(hello_long)
{
    RETURN_LONG(42);
}

PHP_FUNCTION(hello_double)
{
    RETURN_DOUBLE(3.1415926535);
}

PHP_FUNCTION(hello_bool)
{
    RETURN_BOOL(1);
}

PHP_FUNCTION(hello_null)
{
    RETURN_NULL();
}

    你同样需要在头文件php_hello.h hello_world() 的旁边添加这些方法的原型,拿得创建过程能正确执行:

PHP_FUNCTION(hello_world);
PHP_FUNCTION(hello_long);
PHP_FUNCTION(hello_double);
PHP_FUNCTION(hello_bool);
PHP_FUNCTION(hello_null);

    因为你没有修改config.m4文件,所以技术上这次跳过phpize和./configure这两个步骤直接make是安全的。然后,在本游戏的这个阶段,我还是要求你从头把三个步骤都执行一遍以确认活干得漂亮。另外,最后一步的时候,你应该执行make clean all而不是简单的执行make,来确保所有源文件重建。再次声明,现在的改动上述这些步骤是非必须的,但是会更安全更清晰。模块建好后,再拷贝到你的扩展目录下,替换旧版本。

    这个时候你可以再次调用你的PHP解析器,运行一段简单的脚本来试试你刚刚添加的方法。事实上,为何不现在不试呢?我等着呢。。。

    试完了?很好。如果你使用 var_dump() 而不是 echo 来查看每个函数的返回值你也许会发现"hello_bool()"返回true。这就是值1在"RETURN_BOOL()"所表示的。就像在PHP脚本里,整型值0 等于 FALSE, 但是其他所有的整形值都等于 TRUE扩展作者经常使用"1"作这一系列的约定,我们希望你也这样,但是也不必拘泥于此。为了更具可读性, RETURN_TRUE 和  RETURN_FALSE 两个宏也可使用;现在再次修改"hello_bool()",这次使用"RETURN_TRUE":

PHP_FUNCTION(hello_bool){
    RETURN_TRUE;
}

    注意这里没有使用括号哦。 RETURN_TRUERETURN_FALSE 与其他宏RETURN_*() 格式的变体,所以这里注意别被捕获。

    你也许注意到上面这些代码样品我们都没有传0和1什来表示这些值是否需要被拷贝。这是因为没有额外的内存(变量容器之外——我们将在第2部分深入)需要被分配或释放,因为这些标量都很简单很小。

    还有另外三种返回类型: RESOURCE (例如"mysql_connect()","fsockopen()"和"ftp_connect()"的返回值), ARRAY (也称为HASH),还有 OBJECT (通过关键词new返回)。我们将会在第二章更深入变量学习的时候了解这一系列。

INI Settings

    Zend 引擎提供了两个方式处理 INI 变量

    我们先看一下较简单的一种,而更全面、更复杂的方式,等以后你有机会接触全局变量再说。

    现在我们想在php.ini中定义一个变量,"hello.greeting", 用来处理你在"hello_function()"函数里用来打招呼的变量。你需要在hello.cphp_hello.h中添另一些东西,并且hello_module_entry 结构也得有所变化。以加入下面这些原型到php_hello.h的用户空间方法原型旁边:

PHP_MINIT_FUNCTION(hello);
PHP_MSHUTDOWN_FUNCTION(hello); 

PHP_FUNCTION(hello_world);
PHP_FUNCTION(hello_long);
PHP_FUNCTION(hello_double);
PHP_FUNCTION(hello_bool);
PHP_FUNCTION(hello_null);

    现在到hello.c中用下面这串代码覆盖当前版本的hello_module_entry:

zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_HELLO_WORLD_EXTNAME,
    hello_functions,
    PHP_MINIT(hello),
    PHP_MSHUTDOWN(hello),
    NULL,
    NULL,
    NULL,
#if ZEND_MODULE_API_NO >= 20010901
    PHP_HELLO_WORLD_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};

PHP_INI_BEGIN()
PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)
PHP_INI_END()

PHP_MINIT_FUNCTION(hello)
{
    REGISTER_INI_ENTRIES();
    return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(hello)
{
    UNREGISTER_INI_ENTRIES();
    return SUCCESS;
}

    现在,你只需要在hello.c顶部剩下的 #include 上加一个 #include 来包含支持INI 的正确的头:

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

#include "php.h"
#include "php_ini.h
"#include "php_hello.h"

    最后,我们修改 hello_world 方法来使用INI 值:

PHP_FUNCTION(hello_world)
{
    RETURN_STRING(INI_STR("hello.greeting"), 1);
}

    注意,你复制了来自 INI_STR()的返回值。因为这涉及到PHP变量栈都关心的问题,这是一个静态变量。实际上,如果你试图去修改这个方法返回的值,PHP运行环境会变得不稳定甚至可能崩溃。

    首次修改的部分包含了两个你需要熟悉的方法:MINIT MSHUTDOWN。正如上面提到的,这两个方法分别是在SAPI层初始启动和最后关闭时调用的。他们不在请求之间也不在请求之中调用。在这个示例中,你用他们注册在你扩展中定义的php.ini选项。在本文的后面,你将学习如何使用"MINIT"和"MSHUTDOWN"方法来注册资源、对象和流句柄。

    在你的方法hellow_world()中使用"INI_STR()"来取回当前"hello.greeting"项的值作为一个字符串。那些其他的已有方法取值作为长整型,浮点型和布尔型,如下表所示,"ORIG"补充了其他方法,能提供从 INI设置中引用的值(在被.htaccess或者 ini_set() 修改之前的值)。

    

Current Value Original Value Type
INI_STR(name) INI_ORIG_STR(name) char * (NULL terminated)
INI_INT(name) INI_ORIG_INT(name) signed long
INI_FLT(name) INI_ORIG_FLT(name) signed double
INI_BOOL(name) INI_ORIG_BOOL(name) zend_bool

전달 PHP_INI_ENTRY() 의 첫 번째 매개변수는 php.ini 문자열의 옵션 이름을 포함하는 문자입니다. . 네임스페이스 충돌을 방지하려면 메서드와 동일한 규칙을 사용해야 합니다. 즉, 지금은 "hello.greeting"과 같이 모든 값에 확장 이름을 붙이는 것이 일반적인 규칙입니다. 확장자는 이름의 더 설명적인 부분에 대한 이름 및 ini 설정이며 별도입니다.

두 번째 매개변수는 초기값이며 일반적으로 숫자 여부에 관계없이 char* 문자열로 사용됩니다. 이는 주로 .ini 파일의 값이 실제로 텍스트로 기본 설정되어 있기 때문입니다. 텍스트 파일입니다. 스크립트에서 INI_INT(), INI_FLT(), 또는 INI_BOOL() 을 사용하여 수행할 수 있습니다. 유형 변환.

전달된 세 번째 매개변수는 액세스 모드 수정자입니다. INI을 언제, 어디서 수정할 수 있는지 결정하는 데 사용되는 비트마스크입니다. register_globals과 같은 일부 경우 는 스크립트에서 ini_set() 을 사용하여 수정할 수 없습니다. 이 구성은 다음에서만 사용할 수 있기 때문입니다. 스크립트를 시작하기 전에 요청에 실행할 기회를 제공합니다. 또한 allow_url_fopen는 ini_set() 이든 .htaccess 지시문이든 공유 호스팅 환경에서 사용자가 수정하는 것을 허용하지 않는 관리 항목입니다. 이 매개변수의 또 다른 일반적인 값은 PHP_INI_ALL이어야 하며 이는 이 값이 어디에서나 수정될 수 있음을 나타냅니다. PHP_INI_SYSTEM|PHP_INI_PERDIR, 도 있는데, 이는 이 구성을 php.ini 파일에서 설정할 수 있고 Apache 지침을 통해 .htaccess 파일에서도 수정할 수 있음을 의미하지만, ini_set()수정 불가. 게다가 PHP_INI_SYSTEM이 있는데, 이는 php.ini 파일에서만 값을 수정할 수 있고 다른 곳에서는 수정할 수 없다는 의미입니다.

네 번째 매개변수를 건너뛰고 콜백 메서드에 전달할 값을 클릭하기만 하면 ini 구성이 수정될 때마다 호출됩니다. ini_set(). 이를 통해 확장 프로그램은 수정되는 설정을 보다 정확하게 제어하거나 새 구성을 수정하여 해당 동작을 트리거할 수 있습니다.

전역 변수

    通常,扩展需要在特定的请求里跟踪变量的值,使之独立于并发请求。在无线程的SAPI中这可能比较简单:只需要在源文件中声明一个全局变量,在需要时调用。然而麻烦在于PHP被设计运行于线程级的web服务器(如Apache 2 和 IIS),这就需要保证一个线程中的全局变量与其他线程中的分离。PHP通过使用TSRM(Thread Safe Resource Management)抽象层,大大地简化了这一操作,有时被称为ZTS(Zend Thread Safety)。实际 上,到现在你已经使用了一部分TSRM了,虽然你对其不甚了解。(不要急着去搜索,通过这一系列的进展,你会发现它无处不在。)

    创建线程安全全局变量的第一步,跟创建其他全局变量一样,声明它。为了实现这个例子,你将声明一个以 long 型开始,值为 0 的全局变量。每次hello_long() 方法被调用时,我们增值该变量变返回。下面这段php_hello.h中的代码放在#define PHP_HELLO_H 模块后面:

#ifdef ZTS
#include "TSRM.h"
#endif

ZEND_BEGIN_MODULE_GLOBALS(hello)
    login counter;
ZEND_END_MODULE_GLOBALS(hello)

#ifdef ZTS
#define HELLO_G(v) TSRM(hello_globals_id, zend_hello_globals *, v)
#else
#define HELLO_G(v) (hello_globals.v)
#endif

    这次你还将用到 RINIT 方法,因此得在头文件中声明它的原型:

PHP_MINIT_FUNCTION(hello);
PHP_MSHUTDOWN_FUNCTION(hello);
PHP_RINIT_FUNCTION(hello);

    现在 到hello.c中添加下面这段代码到include 模块后:

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

#include "php.h"
#include "php_ini.h"
#include "php_hello.h"

ZEND_DECLARE_MODULE_GLOBALS(hello)

    修改 hello_module_entry 添加PHP_RINIT(hello):

zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_HELLO_WORLD_EXTNAME,
    hello_functions,
    PHP_MINIT(hello),
    PHP_MSHUTDOWN(hello),
    PHP_RINIT(hello),
    NULL,
    NULL,
#if ZEND_MODULE_API_NO >= 20010901
    PHP_HELLO_WORLD_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};

    修改你的 MINIT 方法,添加另一对方法,来处理初始化启动请求:

static void
php_hello_init_globals(zend_hello_globals *hello_globals)
{
}

PHP_RINIT_FUNCTION(hello)
{
    HELLO_G(counter) = 0;
    return SUCCESS;
}

PHP_MINIT_FUNCTION(hello)
{
    ZEND_INIT_MODULE_GLOBALS(hello, php_hello_init_globals, NULL);
    REGISTER_INI_ENTRIES();
    
    return SUCCESS;
}

    最后,修改你的 hello_long() 方法来使用这个值:

PHP_FUNCTION(hello_long)
{
    HELLO_G(counter)++;
    
    RETURN_LONG(HELLO_G(counter));
}

    在添加到php_hello.h的代码里,使用了一对宏ZEND_BEGIN_MODULE_GLOBALS() 和 ZEND_END_MODULE_GLOBALS() 创建了一个包含一个 long变量的结构体 zend_hello_globals 。然后根据条件是否处于一个无线程环境定义了是从线程池取值还是仅从全局范围取值的宏 HELLO_G() 。在hello.c中,我们使用 ZEND_DECLARE_MODULE_GLOBALS() 来创建了一个zend_hello_globals结构的实例,作为一个全局变量(如果在非线程安全中创建)或者一个线程资源池的一员。作为扩展作者,这个区别我们是不用但心的,Zend 引擎会帮我们处理好的。最后,在 MINI ,使用 ZEND_INIT_MODULE_GLOBALS() 分配了一个线程安全资源id——现在还不用担心这个是啥。你也许注意到 php_hello_init_globals()并没有作什么,但是我们声明 RINIT 时初始化counter为 0,为何?关键在于这两个函数何时被调用。php_hello_init_globals() 仅在一个新进程或者线程启动时调用;然而一个进程可以处理多个请求,所以使用这个方法初始化我们的counter为 0 只会在第一个页面请求时调用。接下来的请求到同一个进程的页面会一直使用这个已存储的值,因为不会从 0 开始计数。为了在每个页面请求中初始化counter为 0 ,我们实现了 RINIT 方法,如前所述的在页面请求之前实现。我们在这里包含php_hello_init_globals() 方法是因为一会就要使用到它,传一个 NULL 给ZEND_INIT_MODULE_GLOBALS() 来初始化函数会在非线程平台导致一个段错误 。

    INI设置 vs 全局变量

    如果你回观前文,一个在 PHP_INI_ENTRY() 里声明的php.ini是作为字符串转换成其他需要的类型,通过 INI_INT(), INI_FLT()和 INI_BOOL()。对某些配置,这代表在脚本执行的过程中会做大量重复的工作一遍又一遍的读这个值。幸运的是,可以指定ZE使用特殊的数据类型存储 INI 值,只在其值改变时做类型转换。让我们通过声明另一个 INI 值来试一下这个功能,这次使用布尔型标明计数器应该增还是减。首先修改php_hello.h下的MODULE_GLOBALS 模块如下:

ZEND_BEGIN_MODULE_GLOBAL(hello)
    login counter;
    zend_bool direction;
ZEND_ENG_MODULE_GLOBALS(hello)

    然后,通过修改PHP_INI_BEGIN() 模块来声明 INI 自己的值:

PHP_INI_BEGIN()

    PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)
    STD_PHP_INI_ENTRY("hello.direction", "1", PHP_INI_ALL, OnUpdateBool, direction, zend_hello_globals, hello_globals)
    
PHP_INI_END()

    现在在 init_globals 方法里初始化配置:

static void php_hello_init_globals(zend_hello_globals *hello_globals)
{
    hello_globals->direction = 1;
}

    最后,在hello_long()中使用这个配置的值来决定是自增还是自减:

PHP_FUNCTION(hello_long)
{
    if (HELLO_G(direction)) {
        HELLO_G(counter)++;
    } else {
        HELLO_G(counter)--;
    }    
    
    RETURN_LONG(HELLO_G(counter));
}

    就是这样,我们在INI_ENTRY 里指定的方法 OnUpdateBool 方法会自动转换php.ini, .htaccess或者脚本中通过 ini_set() 指定的值到相关的 TRUE/FALSE 值当你直接通过脚本访问的时候。STD_PHP_INI_ENTRY 的最后三个参数告诉PHP去修改哪个全局变量,我们扩展的全局变量的结构体是什么样,以及他们包含的全局范围的名称。

    完整性检查

    到现在,我们的三个文件里的内容应该如下所列的(为了可阅读性,一些条目被移到了一起)。

    config.m4

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

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

    php_hello.h

#ifndef PHP_HELLO_H
#define PHP_HELLO_H 1
#ifdef ZTS
#include "TSRM.h"
#endif
ZEND_BEGIN_MODULE_GLOBALS(hello)
    long counter;
    zend_bool direction;
ZEND_END_MODULE_GLOBALS(hello)
#ifdef ZTS
#define HELLO_G(v) TSRMG(hello_globals_id, zend_hello_globals *, v)
#else
#define HELLO_G(v) (hello_globals.v)
#endif
#define PHP_HELLO_WORLD_VERSION "1.0"
#define PHP_HELLO_WORLD_EXTNAME "hello"
PHP_MINIT_FUNCTION(hello);
PHP_MSHUTDOWN_FUNCTION(hello);
PHP_RINIT_FUNCTION(hello);
PHP_FUNCTION(hello_world);
PHP_FUNCTION(hello_long);
PHP_FUNCTION(hello_double);
PHP_FUNCTION(hello_bool);
PHP_FUNCTION(hello_null);
extern zend_module_entry hello_module_entry;
#define phpext_hello_ptr &hello_module_entry
#endif

    hello.c

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "php_hello.h"
ZEND_DECLARE_MODULE_GLOBALS(hello)
static function_entry hello_functions[] = {
    PHP_FE(hello_world, NULL)
    PHP_FE(hello_long, NULL)
    PHP_FE(hello_double, NULL)
    PHP_FE(hello_bool, NULL)
    PHP_FE(hello_null, NULL)
    {NULL, NULL, NULL}
};
zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_HELLO_WORLD_EXTNAME,
    hello_functions,
    PHP_MINIT(hello),
    PHP_MSHUTDOWN(hello),
    PHP_RINIT(hello),
    NULL,
    NULL,
#if ZEND_MODULE_API_NO >= 20010901
    PHP_HELLO_WORLD_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_HELLO
ZEND_GET_MODULE(hello)
#endif
PHP_INI_BEGIN()
    PHP_INI_ENTRY("hello.greeting", "Hello World",
PHP_INI_ALL, NULL)
    STD_PHP_INI_ENTRY("hello.direction", "1", PHP_INI_ALL,
OnUpdateBool, direction, zend_hello_globals, hello_globals)
PHP_INI_END()
static void php_hello_init_globals(zend_hello_globals *hello_globals)
{
    hello_globals->direction = 1;
}
PHP_RINIT_FUNCTION(hello)
{
    HELLO_G(counter) = 0;
    return SUCCESS;
}
PHP_MINIT_FUNCTION(hello)
{
    ZEND_INIT_MODULE_GLOBALS(hello, php_hello_init_globals,
NULL);
    REGISTER_INI_ENTRIES();
    return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(hello)
{
    UNREGISTER_INI_ENTRIES();
    return SUCCESS;
}
PHP_FUNCTION(hello_world)
{
    RETURN_STRING("Hello World", 1);
}
PHP_FUNCTION(hello_long)
{
    if (HELLO_G(direction)) {
        HELLO_G(counter)++;
    } else {
        HELLO_G(counter)--;
    }
    RETURN_LONG(HELLO_G(counter));
}
PHP_FUNCTION(hello_double)
{
    RETURN_DOUBLE(3.1415926535);
}
PHP_FUNCTION(hello_bool)
{
    RETURN_BOOL(1);
}
PHP_FUNCTION(hello_null)
{
    RETURN_NULL();
}

    接下来做什么?

    在这教程中我们开发了一个简单的PHP扩展,导出方法,返回值,声明了 INI 配置并且在一个请求中跟踪他的内核状态。

    下一章我们研究PHP变量的内核结构,以及变量如何存储、跟踪和在脚本环境中修改。当一个函数被调用时我们使用zend_parse_parameters 从程序获取参数,接着学习返回更复杂的结果的方法,包括本章提到的 arrayobject 

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.