The INI framework in PHP 4.0

From: Date: Fri, 26 May 2000 12:36:07 +0000
Subject: The INI framework in PHP 4.0
Groups: php.dev 
Request: Send a blank email to php-dev+get-19495@lists.php.net to get a copy of this message
One of the least known new features of the new PHP 4.0 API is its INI framework. I was under the impression people understood more or less what it does, but yesterday I found that the session module wasn't using it (at least not properly); Since not using this INI framework results in a very significant performance loss, I think it's time to say something about it... Some history ------------ In the beginning, there was the configuration hash. This hash was a direct mapping of the php3.ini file, and there was nothing smart about it. In the days of PHP 3.0, we had a very large and ugly structure, php3_ini, which included entries for most of the configurable directives in PHP. It worked like this - on every request, we would check whether there's a value for each relevant directive - if there is, we'd assign it to the struct member. If not - we'd assign a default value. Most of the 'application' code used this structure, and almost never touched the configuration hash directly. There were many drawbacks in this approach: - It was dead slow. For every given directive, there had to be a hash lookup on every request. In the Apache module, there had to be quite a lot of work in the .htaccess handlers, to properly handle all of the different directives, which also reduced performance. - It was plain ugly. For every new directive, you had to add a few lines of code to main.c and mod_php3.c. - It encouraged inconsistency. More often than not, new directives were only added to main.c, and were thus unsupported as Apache module directives. Each and every change in their behavior had to be made in two different places. - That's more of a personal opinion, but the fact that there were many many different Apache directives (all prefixed with php3_), instead of just one Apache directive, that accepts the PHP directive name as an argument, was fairly ugly, again, IMHO. The situation today ------------------- That was more than enough motivation to fix the situation. php_ini.c was born. Designed to fix all of the problems in the previous approach, it behaves as follows: - The configuration hash remained the same, it still holds a direct mapping of the php.ini file. - A new hash has been introduced, the php_ini hash. This hash maps a directive name to a struct that includes all of the necessary information about it - it's current value, it's default value, what to do if the value changes, who can change this value, etc. I do realize that the names of the hashes (php_ini vs. the configuration hash) appear to be reversed; It's for historical reasons... - A set of functions and macros has been introduced, to replace the ugly specialized code in main.c and mod_php3.c, and allow each module to locally control its INI directives. How did it solve the defficiencies in the old method? Lets see them one by one: - Performance. The new INI framework allows you to specify a callback function that will be called whenever a given directive changes. This allows you to save the value for directives that are used fairly often in a 'cache', in a struct that requires no hash lookups. For this purpose, you can use the STD_PHP_INI_ENTRY() and STD_PHP_INI_BOOLEAN() macros. Look at main.c, around line 183 onwards, there are lots of examples there. A standard INI entry like that will call the specified callback function (e.g., OnUpdateBool()), and provide it with the information it needs to store the updated INI value in the proper place in a global (or a ZTS-global) struct. For example, if you look at the expose_php entry - whenever this value is modified (e.g., by specifying 'php_flag expose_php Off' in a .htaccess file), OnUpdateBool() will be called, and depending on whether we're in thread-safe mode or not, OnUpdateBool() will be provided with a pointer to the global php_globals struct, or the ts_resource that's registered for the php_globals struct. It'll then modify php_globals.expose_php to reflect the new value. The INI framework remembers that the value was modified, and at shutdown, it'll restore the original value of the INI entry, and to make sure the cache is consistent, it'll call the callback function OnUpdateBool() as well. This means that if the user doesn't override the defaults for the configuration directives (which is usually the case), there's no added overhead. Only when the user actually overrides a default value, a hash lookup is performed. Of course, the callback functions can be used to conduct all sorts of actions as well, or implement additional logic. Take a look at the error_reporting directive, and how it uses OnSetErrorReporting() to implement a default value that's a bit difficult to put into the macro (E_ALL & ~E_NOTICE, whereas the macros only accept string constants as default values), as well as update Zend's EG(), because currently Zend doesn't use the INI framework directly, and would otherwise not know about the change. If changing a given INI entry has side effects, this is the best way to implement it. - The new system is pretty clean. You almost never have to write specialized code to handle INI directives. If you have to implement specialized code for a specific directive, you can do it by writing a self-contained callback function. - It killed the inconsistencies. INI entries are only added in one place, and are automatically supported in any supported configuration source (php.ini, httpd.conf, .htaccess files, Windows registry, etc). For security, you can specify that a certain directive can only be modified from specific INI sources: PHP_INI_USER - The user can modify the value for the directive in runtime, using ini_set() PHP_INI_PERDIR - The user can modify the value for the directive by specifying per-directory values in an .htaccess file, or the Windows registry PHP_INI_SYSTEM - php.ini and the server-wide configuration file, httpd.conf PHP_INI_ALL - PHP_INI_USER|PHP_INI_PERDIR|PHP_INI_SYSTEM - all of the above. - Thanks to the other changes, it was possible to cut down the number of Apache directives to just four: php_value [directive_name] [directive_value] php_flag [directive_name] [directive_value] php_admin_value [directive_name] [directive_value] php_admin_flag [directive_name] [directive_value] php_value can be used to specify any values, whereas php_flag is only used for boolean values, and natively supports On/Off. php_admin_value and php_admin_flag are equivalent, only they run in PHP_INI_SYSTEM context, whereas php_value and php_flag run in PHP_INI_PERDIR context. Usage of the framework is pretty simple. As always, you can take the MySQL module as a reference on how to use this system. It basically consists of setting up the list of INI entries for your module: PHP_INI_BEGIN() ... PHP_INI_END() Calling REGISTER_INI_ENTRIES() at the module init (MINIT) function, and calling UNREGISTER_INI_ENTRIES() at the module shutdown (MSHUTDOWN) function. That's it. I haven't looked at the source files for the various modules, but I assume that if the session module wasn't using it, there are probably others. If you're the author of one such module, and especially if your current per-request function implementation looks up the values for the various configurable directives, please take the time to convert it to use the INI framework. This will significantly cut down PHP's startup time. As for the session module, there's still quite some work that can be done on reducing its startup overhead, but that's for another letter... Zeev -- Zeev Suraski <zeev@zend.com> http://www.zend.com/

« previous php.dev (#19495) next »