The INI framework in PHP 4.0
| From: | Zeev Suraski | 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/