📜 ⬆️ ⬇️

Creating PHP Extensions: An Introduction to PHP and Zend

Translation of Sara Golemon 's article “Extension Writing Part I: Introduction to PHP and Zend” .



Introduction


If you are reading this article, most likely you have some interest in creating extensions for the PHP language. If not ... maybe when you read this article, you will find that interest in yourself, despite the fact that you didn’t even know about it!
')
The material in this article implies an introduction to both the PHP language itself and the language in which the PHP interpreter is written: C.

Let's start by identifying the reasons why you want to write an extension for PHP:
  1. The existence of any library or a specific OS call that cannot be made from PHP directly due to the level of abstraction adopted in the language;
  2. You want to make PHP work in a non-standard way;
  3. You already have a solution written in PHP, but you know that it can be faster, more compact and consume less memory in the process;
  4. You have a special code that you would like to sell. However, it is important that the buyer could run your code, but not watch the source.

All the reasons listed above are quite adequate, but when creating an extension, first of all you should understand that this is primarily an extension .



What is Expansion?


If you had to use PHP, then you used and extensions. With a few exceptions, each available function in PHP is grouped into one or another extension. The main part of the functions (more than 400) is included in the standard extension . PHP source codes are distributed with about 86 extensions, having about 30 functions each. Considering, we get somewhere in 2500 functions in the amount. If this is not enough, the PECL repository offers over 100 additional extensions, even more can be found on the vast expanses of the Internet.

“Well, considering all this variety of functions that live in extensions, then remains outside the extensions?” - you ask. “What extensions expand? What is the core of PHP ?

The PHP core is implemented as 2 separate parts. The technical part of the language is presented in the form of Zend Engine (ZE). ZE is responsible for converting a human-readable script into computer-friendly tokens, and then executes them. In addition, the ZE is responsible for managing the memory, scopes of variables, handling function calls.
The second part of the kernel is what is directly called the “ PHP core ”. It is responsible for interacting with the SAPI layer (Server Application Programming Interface, PHP interface with other server software - CLI, CGI, Apache, and so on). In addition, the kernel implements a generic control layer for safe_mode and open_basedir checks (features are depricated from version 5.3), as well as a thread layer that associates file and network I / O operations with the fopen, fread and fwrite functions.



Life cycle


When a given SAPI starts up (for example, when Apache server is started by the command / usr / local / apache / bin / apachectl start ), PHP starts its work by starting the kernel subsystem. By the end of the startup procedure, it loads the code for each extension and calls its Module Initialization (MINIT) function. This gives each extension the ability to initialize internal variables, allocate memory for resources, register resource handlers and its functions in the ZE, so that when you call a function of this extension, ZE knows what code to execute.

Next, PHP waits for the page processing request from the SAPI layer. In the case of SAPI CGI or CLI, this happens immediately and only once. In the case of SAPI Apache, IIS or another full-fledged web server, a request to process a page occurs every time a page is requested (possibly competitive) by a remote user. However, regardless of how the request arrived, its processing begins with the fact that the PHP core asks the ZE to set up the environment to run the script, after which it calls the Request Initialization (RINIT) function for each extension. RINIT gives extensions the ability to set specific environment variables, allocate memory for specific request resources, and perform other tasks. A vivid example of the RINIT function in action can be the session extension, in which, with the session.auto_star t setting turned on, the RINIT function automatically causes the execution of the session_start function and initializes the $ _SESSION variable.

After the request is initialized, ZE translates the PHP script into tokens and then into opcodes that it can execute. If any of these opcodes request a function call from the extension, the ZE forms the arguments for calling this function and temporarily transfers control to it until it completes.

After the script has completed its execution, PHP calls the Request Shutdown (RSHUTDOWN) function for each extension to do everything necessary to complete the cleanup (for example, saving session variables to disk). The next step ZE performs the cleanup process (also known as garbage collection), which actually performs the unset method for each variable used in the executed script (since PHP 5.3, the garbage collection mechanism has been greatly improved).

After completing the processing of the request, PHP is waiting for the SAPI to either request a processing of another script, or a signal for completion. In the case of CGI or CLI SAPI, the “next request” is not possible; therefore, SAPI initiates PHP shutdown immediately. In the process of completing PHP, it iterates through all the extensions and calls the Module Shutdown (MSHUTDOWN) function for each, and then completes its own kernel subsystem.

This process may seem a bit confusing at first, but if you are immersed in the work on extensions, you will gradually feel it.



Memory allocation


In order to avoid memory leaks in poorly written extensions, ZE uses its own internal memory management mechanism, based on an additional flag to determine the lifetime of the data. Permanent ( persistent ) memory allocation means that memory will be allocated more than the time it takes to process a single page request. Non -persistent memory allocation means freeing up memory after processing a request, regardless of whether the freeing function was called. For example, the allocation of memory for user variables is variable, since upon completion of the processing of the request they become useless.

Although the extension in theory may place the ZE on freeing non-permanent memory automatically upon completion of each request, this is not recommended. The allocated memory will remain unclaimed for a long time, the resources associated with this memory will be less likely to be closed correctly, and, in the end, creating confusion with the release of memory is a bad practice. As you will see later, making sure that all the data is cleaned up is easy enough.

Let's briefly compare the traditional memory allocation functions (which should be used only when working with external libraries) with the functions of permanent and non-constant memory allocation in PHP / ZE.

TraditionalInconsistentPermanent
malloc (count)emalloc (count)pemalloc (count, 1) *
calloc (count, num)ecalloc (count, 1)pecalloc (count, num, 1)
strdup (str)estrdup (str)pestrdup (str, 1)
strndup (str, len)estrndup (str, len)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)
* The pemalloc family of functions accepts a “constancy” flag as a parameter, which allows them to behave like their non-permanent counterparts.
For example: emalloc (1234) is the same as pemalloc (1234, 0)
** safe_emalloc () and (in PHP 5) safe_pemalloc implement an additional check for integer overflows.




Setting up and building the environment


Now that you are familiar with the theory of the work of PHP and Zend Engine, I bet you can’t wait to dive into the work and start doing something. However, before that you need to acquire some utilities for building and set up the environment for your purposes.

First of all, you need PHP itself and the set of build tools needed for PHP. If you don’t have to build PHP from source, I suggest taking a look at this article . Despite the fact that the use of a binary package with PHP source files may seem tempting, such builds often lack two important parameters of the ./configure program, which are very useful during the development process. The first one is enabled-debug . This option compiles PHP with additional debugging information in executable files, so if a segfault error occurs, you can get a kernel dump and use the gdb debugger to figure out where and why the error occurred.
The name of the second option depends on which version of PHP you are going to work with. In PHP 4.3, it is called --enable-experimental-zts , since PHP 5, it has been renamed to --enable-maintainer-zts . This option will make PHP think that it works in a multithreaded (multithread) environment, and allows you to catch common errors that are invisible in a non-threaded environment, but will cause your extension to be unstable in a multi-threaded environment.
By compiling PHP with additional options and installing it on a development server (or workstation), you can start creating your first extension.



Hello world


What is the introductory article on programming can be considered complete without an example of the now obligatory program Hello World? In this case, we’ll look at creating an extension that adds a single function to PHP that returns a string with the word “Hello World.” In PHP, you would probably implement it like this:
<?php

function hello_world() {
return 'Hello World' ;
}

?>

Now let's make a PHP extension from this function. First of all, create a hello subdirectory in the ext / directory directory of the PHP source tree and make a cd into it. In principle, this directory can be located anywhere inside or outside the source directory tree, but you should put it here to be able to refer to it from the article in the future. Here you need to create three files:
  1. The configuration file ( config.m4 ) used by the phpize utility to prepare the extension for compilation:
    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

  2. a header file ( php_hello.h ) containing instructions used by PHP to load extensions:
    #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



  3. source file ( hello.c ) containing the body of the hello_world function:
    #ifdef HAVE_CONFIG_H
    #include "config.h"
    #endif

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

    static function_entry hello_functions[] = {
    PHP_FE(hello_world, 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,
    NULL,
    NULL,
    NULL,
    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_FUNCTION(hello_world)
    {
    RETURN_STRING( "Hello World" , 1);
    }



Most of the code that you see in the previous example is just a kind of “glue” - a protocol for registering an extension in PHP and establishing a dialogue between them. The real code is only the last 4 lines in the file hello.c , which perform actions at a level that can be invoked from a user script. Undoubtedly, this code is quite similar to the PHP code that we saw earlier, and can be easily disassembled into parts:

Recall that the ZE uses an advanced memory management mechanism that verifies that all requested resources are released at the time the script is finished. In the world of memory management, the re-release of the same block of memory is considered big ah-ah-ah . Such an action is called “double freeing” and is a common cause of segmentation errors, as it causes the program to try to gain access to a block of memory that no longer belongs to it.
Simply put, so that ZE does not start re-freeing the memory occupied by a static string (which is “Hello World” in our example), the hello_world function should return a copy of the string each time it is called.
In principle, the RETURN_STRING function can take responsibility for the fact that any string passed to it must be copied, so that it would be possible to safely free its memory later. However, since allocating memory for a string, copying a string, and returning a copy as a result is not a standard behavior for internal ZE functions, RETURN_STRING allows you to specify whether you need to make a copy of a variable or not using the second argument. For a clearer description of this concept, pay attention to the following code fragment, which is functionally identical to the previous one:
PHP_FUNCTION(hello_world)
{
char *str;

str = estrdup( "Hello World" );
RETURN_STRING(str, 0);
}



In this example, you manually allocated non-permanent memory for the string “Hello World”, which you passed back to the calling script using the RETURN_STRING function , specifying 0 as the second parameter, which causes the function not to make a copy of the string, as it can use ours.



Extension build


The final step in this exercise will be to build your extension as a dynamically loadable module. If you copied the previous examples correctly, all that remains is to run three commands from the ext / hello / directory:
$ phpize
$ ./configure --enable-hello
$ make

Please note that for the phpize utility to work correctly, you must have the m4 and autoconf utilities.
After running all these commands, you should have the file hello.so in the ext / hello / modules / folder. Now, like any other PHP extension, you can simply copy its directory with extensions (the default is / usr / local / lib / php / extensions / , but just in case, check the php.ini settings or use the php-config - command -extension-dir ) and add the line extension = hello.so to php.ini to initialize the loading of the extension when PHP starts. For SAPI type CGI / CLI, this will be the next script launch, for SAPI web servers like Apache this will be the next server restart. Let's try calling the function from the command line:
$ php –r 'echo hello_world();'

If everything goes as it should, you will see the string Hello World as the result of the script, since the hello_world function from the loaded extension returns a string, and the echo command displays what was passed to the input (in this case, the result of the hello_world function ).

In this example, we returned a string, other scalar data types can be returned on a similar basis: RETURN_LONG for integer values, RETURN_DOUBLE for floating-point numbers, RETURN_BOOL for TRUE / FALSE and RETURN_NULL for, you guessed it, NULL-values. Let's look at each of them in action by adding lines with the PHP_FE macro to the function_entity structure and the corresponding PHP_FUNCTION macros in the hello.c file:
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();
}



In addition, we will have to add prototypes for these functions next to the prototype for the hello_world function in the php_hello.h header file in order for the build process to go without problems:
PHP_FUNCTION(hello_world);
PHP_FUNCTION(hello_long);
PHP_FUNCTION(hello_double);
PHP_FUNCTION(hello_bool);
PHP_FUNCTION(hello_null);



Since you did not make any changes to the config.m4 file, technically there is no need to repeat the phpize and ./configure steps, but you can immediately proceed to the execution of the make command . However, this time I will ask you to go through all three steps of the assembly anew in order to make sure that no problems have arisen. In addition, as the last step, you can use the command make clean all instead of make to be sure that all the files with the sources will be rebuilt. I repeat that this is not necessary due to the type of changes you made, but it is better to be safe than stumble upon an error. After the module is assembled, you need to copy it into the extensions directory, replacing the old version with it.

Now you can call the PHP interpreter again, passing simple scripts to it in order to test the newly added functions. In fact - why not do it now? I'll wait for you here ...

Is it done? Good. If you used var_dump instead of echo to view the result of each function, you may have noticed that hello_bool returns TRUE. This is the result of the equality of the 1st argument of the function. As with PHP scripts, the integer value 0 is equivalent to FALSE, while any other number is equivalent to TRUE. The authors of extensions often use the agreement according to which this number is 1. It is desirable, but not necessary, that you also adhere to this agreement. In addition, the RETURN_TRUE and RETURN_FALSE macros are available for more convenience. Below is an example of the hello_bool function using the RETURN_TRUE macro.
PHP_FUNCTION(hello_bool)
{
RETURN_TRUE;
}



Note that no parentheses were used when calling the macro. In this regard, the RETURN_TRUE and RETURN_FALSE macros are different from other RETURN_ * macros, so be careful not to fall for it!

You may have noticed that in all previous examples we do not pass the parameter responsible for creating a copy of the result. The reason for this is that such simple scalar values ​​do not require the allocation or freeing of additional memory.

There are 3 additional return types: RESOURCE (returned, for example, by the functions mysql_connect , fsockopen or ftp_connect ), ARRAY (also known as HASH ) and OBJECT (returned by the keyword new ). We will talk about them later.



INI-settings


Zend Engine provides two approaches for working with INI data. Now we will look at the simplest of them, and will return to the more general one after becoming familiar with global variables.

Suppose you want to declare in the php.ini file the hello.greeting setting for your extension, which will contain the value to be output by the hello_world function. To do this, we will have to add a few changes to the hello.c and php_hello.h files as part of the change in the hello_module_entry structure. Let's start by adding the following prototypes before the list of prototypes of user-defined functions in the php_hello.h file:
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);



Next, open the file hello.c and replace the current version of hello_module_entry with the following:
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;
}



Now you need to add the appropriate #include directive at the beginning of the hello.c file to get the headers for working with INI files:
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

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



The last thing you need to do is modify the hello_world function so that it uses the INI setting:
PHP_FUNCTION(hello_world)
{
RETURN_STRING(INI_STR( "hello.greeting" ), 1);
}



Note that you are creating a copy of the value received from INI_STR . The reason is that the string returned by INI_STR is static. In other words, if you try to change it, the PHP working environment may become unstable or even crash.

The first set of changes in this chapter adds two new methods that you should already be familiar with: MINIT and MSHUTDOWN . As mentioned earlier, these methods are called when the SAPI layer is first launched and in the process of completing its work, respectively. They are not called during or between requests. In this example, they are used to register entries from the php.ini file declared in your extension. Also in these functions, resources, objects and stream handlers can be registered.

In your hello_world function , you use the INI_STR macro to get the current value of the hello.greating entry as a string. There are a number of other functions for obtaining integer values, floating point values, boolean values ​​listed below. In addition, these functions have duplicates with the ORIG suffix, allowing you to get the value of the record as written in the php.ini file (before it could be changed using the .htaccess file or the ini_set function).
present valueOriginal meaningType of
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

Moving on to the function PHP_INI_ENTRY . The first parameter is the string that contains the name of the entry you are interested in the php.ini file . In order to avoid collisions between the names of records in php.ini , you should use the same conventions as in naming functions: the name must begin with a prefix that matches the name of the extension. The agreement also provides that the separator of the extension name from the original setting name in the INI files must be a period. In this case, the name of the setting will look like hello.greeting .
The second parameter is the initial value of the setting, which is always specified as char *, regardless of whether the value is a number or not. This is a consequence of the fact that all the settings in the INI files are inherently textual, since the file itself is textual. Only subsequent use of the INI_INT , INI_FLT or INI_BOOL macros in the script causes a conversion of their types.
The third parameter is the access level modifier. This is a bitmask that determines when and how a given INI setting can be modified. For some settings, such as register_globals , it simply does not make sense to allow changing the value inside the script using the ini_set function, since this setting makes sense only during the preparation of the processing of the request - before the script is given the opportunity to work. Others, such as allow_url_fopen , are administrative settings that users should not have the right to modify either through ini_set or through .htaccess directives. The default value for this parameter is the value of PHP_INI_ALL , indicating that the value of the setting can change anywhere. PHP_INI_SYSTEM | PHP_INI_PERDIR values ​​are also possible, indicating that the setting value can be changed via php.ini or a directive in the .htaccess file, but through the ini_set () function. Or perhaps the value of PHP_INI_SYSTEM , meaning that the setting can only be changed via the php.ini file and nowhere else.

The last fourth parameter allows you to specify a callback function (callback), called when changing settings using the ini_set function. This allows extensions to have more control over the conditions for changing the setting or to call the corresponding function depending on the new setting value.



Global variables


Quite often, an extension needs to process a variable in a separate request, keeping its value independent of other requests that can be processed at the same time. In a non-threaded SAPI, this can be done very simply: just declare a global variable in the source file and access it when you need it. The problem is that since PHP is designed to work with multi-threaded web servers (such as Apache 2 and ISS), it needs to keep the global variables used by one thread separately from the global variables of the other. PHP greatly simplifies this task by using the Thread Safe Resource Manager ( TSRM) abstraction layer, sometimes called ZTS (Zend Thread Safety). In fact, this article has already used parts of the TSRM, which you did not even notice. (Do not try to find them, as long as it is too difficult for you).

The first part of creating a thread-safe global variable, like any other global variable, is in its declaration. As an example, we will declare one global variable of the long type, the initial value of which will be 0. Each time the hello_long function is called, we will increase the value of the global variable and return its value.
Add the following code snippet to the php_hello.h file immediately after the #define PHP_HELLO_H line :
#ifdef ZTS
#include "TSRM.h"
#endif

ZEND_BEGIN_MODULE_GLOBALS(hello)
long counter;
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



You will also have to use the RINIT method, so you need to declare its prototype in the title:
PHP_MINIT_FUNCTION(hello);
PHP_MSHUTDOWN_FUNCTION(hello);
PHP_RINIT_FUNCTION(hello);



Now let's go to the file hello.c and add the following code after the include block:
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

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

ZEND_DECLARE_MODULE_GLOBALS(hello)


Change hello_module_entry by adding PHP_RINIT (hello) to it :
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
};



And change your MINIT function along with adding a few more functions to handle initialization at the start of the request:
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;
}



And finally, you can change the hello_long function by adding a global variable to it:
PHP_FUNCTION(hello_long)
{
HELLO_G(counter)++;

RETURN_LONG(HELLO_G(counter));
}



In the changes you made to the php_hello.h file, you used a couple of macros - ZEND_BEGIN_MODULE_GLOBALS and ZEND_END_MODULE_GLOBALS . With their help, the zend_hello_globals structure was defined, containing one long variable. After that, the HELLO_G macro was defined, which allows, depending on the compilation mode (with or without multithreading), to get the value from the thread pool or just take it from the global scope.
In the hello.c file, using the ZEND_DECLARE_MODULE_GLBALS macro , you declared the zend_hello_globals structure either as a global (non-thread safe) or as a member of the resource pool of the stream. We, as authors of extensions, do not need to think about this mechanism, since ZE takes over all the work. And finally, you use the ZEND_INIT_MODULE_GLOBALS function to allocate a thread-safe resource identifier.
You may have noticed that the php_hello_init_globals function actually does nothing. In addition, the initialization of the counter value 0 was in the function RINIT . Why?
The key to answering this question lies in the moment when these functions are called. The php_hello_init_globals function is called only when starting a new process or thread. However, each process can serve more than one request, so using this function to initialize our counter with a value of 0 will be true only for the first request. A subsequent query to the same process will still work with the old value of the counter and, therefore, will not start the report from scratch. To initialize the counter with a value of 0 for each request, we used the RINIT function, which, as you have already read, is called before each script processing request. We included the php_hello_init_globals function in our code at least due to the fact that passing NULL as the corresponding parameter ZEND_INIT_MODULE_GLOBALS to the init function will result in a segmentation error for non- threading platforms.



INI-settings and global variables


As you already know, the settings in the php.ini file, declared using the PHP_INI_ENTRY function, are read as strings and, if necessary, converted to other formats using the INI_INT , INI_FLT and INI_BOOL functions . However, some settings during script execution may be requested too often, which will lead to a large number of unnecessary read operations from a file. Fortunately, it is possible to instruct ZEs to store INI settings in a particular data type and perform type casting only when the setting value is changed. Let's test this feature in the work by declaring another INI setting, this time of type boolean, which determines whether the counter should currently decrease or increase. We begin by modifying the MODULE_GLOBALS block in the p hp_hello.h file as follows:
ZEND_BEGIN_MODULE_GLOBALS(hello)
long counter;
zend_bool direction;
ZEND_END_MODULE_GLOBALS(hello)



The next step is to declare the INI setting by changing the PHP_INI_BEGIN block:
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()



Now initialize the setting in the init_globals function:
static void php_hello_init_globals(zend_hello_globals *hello_globals)
{
hello_globals->direction = 1;
}



And finally, use the INI-setting value in the hello_long function to select the operation on the counter:
PHP_FUNCTION(hello_long)
{
if (HELLO_G(direction)) {
HELLO_G(counter)++;
} else {
HELLO_G(counter)--;
}

RETURN_LONG(HELLO_G(counter));
}



That's all! Using the OnUpdateBool method (the method is part of the ZE), passed as the third parameter of the STD_PHP_INI_ENTRY macro, the type of the setting value obtained from the php.ini file , .htaccess , or using the ini_set function will be automatically converted to the corresponding TRUE / FALSE value, which you can get right inside the script. The last three parameters of the STD_PHP_INI_ENTRY function tell PHP which global variable to change, what the structure of the global variables of our extension looks like, and the name of the global variable container in which they are contained.



What's next?


In this article, we explored the structure of a simple PHP extension that exports functions that return values, loads and processes INI settings, saving them throughout the processing of a request.

In the next article we will look at the internal structure of PHP variables, how they are stored and processed. Let's work with the zend_parse_parameters function used to get parameters from a program when calling a function, and open ways to return more complex results from a function, such as an array, an object, and a resource.



PS


In some places, the translation has a rather free format, which is a consequence of the difficulties of translation, which I have not been able to fully overcome. In addition, the translation contains a number of minor additions that seemed relevant to me.
The process of building an extension according to the article is verified on the PHP 5.3.2 source .

The author has several articles on this topic: 1 , 2.1 , 2.2 , 3 . The fourth article apparently will not see the light of view due to the publication of the book “Extending and Embedding PHP” by the author of this book .

You can also read about Zend Engine on php.net .

Source: https://habr.com/ru/post/98862/


All Articles