Sunday, February 8, 2009

Apache & PHP: Multiple PHP.ini Configuration Files

I just thought I'd add my little tid-bits of information here as I searched and searched for possible solutions to a formidable problem: How can I use per-directory PHP configuration settings in Apache?

I read through several different work-arounds and tested them thoroughly. I've come to one possible and viable solution.

Solution #1: The information listed here in this documentation section seemed to be the best possible solution. The php_value, php_flag, php_admin_value, and php_admin_flag (settings available in httpd.conf, vhosts.conf, or per-directory .htaccess files) seemed to do the job nicely in each and all of my virtual hosts. It takes a LOT of editing -- and possibly a lot of files -- but the results are efficient and less CPU taxing.

So, if you wanted to set a virtual host's doc_root directive, you'd use something like:
# vhosts.conf
NameVirtualHost *:81
<VirtualHost *:81>
ServerAdmin admin@example.com
ServerName vhost.example.com
DocumentRoot "C:/path/to/doc/root"
ErrorLog "logs/vhost.example.com-errors.log"

# Set the PHP "doc_root" directive
php_value doc_root "C:/path/to/doc/root"

<Directory "C:/path/to/doc/root">
# ... yadda, yadda, yadda ...
# you get the point!
</Directory>
</VirtualHost>
I used the doc_root directive example due to the fact that if you want to use relative paths to include files (i.e. <?php require_once "Classes/MyClass.php"; ?>) in files several folders deep in the tree, you need to tell PHP where to look. By default, if this is not set, or if the include_path directive is not set, PHP only looks in it's own directory (i.e. C:/PHP). However, setting the master PHP configuration file's include_path directive to all the virtual hosts' directories can cause problems, especially if you're including files with the same name in several different virtual hosts (as is in my case where I use my own framework for each of my Web site projects). It also taxes PHP as far as efficiency is concerned. It is for this I started using per-directory PHP.ini configuration files in all of my virtual hosts under IIS. I found that Apache ignores these files. Meanwhile, I found this solution to be the most efficient resource-wise, although time consuming as far as personally.

NOTE: This solution doesn't quite have the effect I had originally intended. For some reason, the per-directory php.ini file is not being loaded per-request to a given virtual host. I'm currently searching for another possibility for solving this issue. In the mean time, I'll leave this up for legacy purposes and to remind myself how quick to conclusion I can be. ;)

Solution #2: The second work-around is to use the PHPRC environment variable configurable in Apache using the SetEnv directive. This directive allows you to set an environment variable that is then passed to any CGI script and/or Server Side Include (SSI) set in any static (x)HTML page (or other document type). In this instance, you'd be telling each PHP-CGI instance where to find its configuration settings. The example would be (coinciding with the previous one):
# vhosts.conf
NameVirtualHost *:81
<VirtualHost *:81>
ServerAdmin admin@example.com
ServerName vhost.example.com
DocumentRoot "C:/path/to/doc/root"
ErrorLog "logs/vhost.example.com-errors.log"

# Set the PHPRC environment variable
SetEnv PHPRC "C:/path/to/doc/root/php.ini"

<Directory "C:/path/to/doc/root">
# ... yadda, yadda, yadda ...
# you get the point!
</Directory>
</VirtualHost>
There are two very good reasons why I don't believe this to be the better solution, although it does work. The first reason is that it requires you to have a php.ini file per directory. As I said before, I don't have much of a problem with this under IIS, but that is simply because in IIS, PHP reads the master php.ini file as well as per-directory configuration files. Apache, however, does not. The issue here is that each and every per-directory configuration file must contain all directives previously set in the master PHP configuration file. In other words, if you have a common include_path directive (for example) set that includes common file locations that you plan to use in conjunction with the current working directory's files accross your virtual hosts, you would have to also implicitly express the already set paths in EVERY php.ini file. Again, this is not the case in IIS. In IIS, you can set all the generalized configuration settings for PHP that remain true accross all of your Web sites and only set the pertinent ones in the per-directory configuration files. In that sense, you could have your include_path directive in your master file set to include common PHP scripts and includes and set your doc_root setting in the per-directory file to include scripts from anywhere in that site's current working directory. Here's an example:

Master php.ini Configuration File:
include_path = ".;C:/PHP/PHP5/pear;C:/Users/Hyponiq/Sites/Framework"

Per-Directory php.ini Configuration File (IIS):
; include_path already implied and settings used under IIS
doc_root = "C:/Users/Hyponiq/Sites/hyponiqs.com"

Per-Directory php.ini Configuration File (Apache):
; include_path is ignored from the master file; in fact, it wasn't even read!
include_path = ".;C:/PHP/PHP5/pear;C:/Users/Hyponiq/Sites/Framework"
doc_root = "C:/Users/Hyponiq/Sites/hyponiqs.com"

Conversely, if you specify an already implicitly set configuration variable in a per-directory file, it is overwritten. This also comes in handy in IIS. Sometimes, you may not need or wish to include a directive set in the master file in all of your virtual hosts, especially one that is more limited than the other. To keep this as short as possible (and it's already lengthy), I'll not go into that. Considering that, however, there are times I could see this solution coming in handy.

The other problem I have with this solution is that in order to use it, you must set the AllowOverride FileInfo directive in the httpd.conf file in order to allow the SetEnv directive to be parsed and utilized in each virtual host. What this means is that Apache then also allows the use of other -- and potentially harmful -- directives as well. What does this mean for server administrators? If you're working in a multi-user environment, such as a Web hosting service, a user may include allowed FileInfo overridden directives that could cause unwanted problems, if used correctly -- or even incorrectly by a novice. To my knowledge (correct me if I'm wrong), there are no Apache configuration directives that allow server administrators to allow or deny specific configuration directives. AllowOverride only limits categories which encompass many directives. Thus, an expert user who knows what they are doing could potentially cause serious damage to a server. In that case, a seriously inexperienced user without any knowledge could accidently mess things up as well.

Well, I hope this helps all of you -- novices and professionals alike.

1 comment:

kgeo said...

Well,

as I wrote there (http://stackoverflow.com/questions/15523860/open-basedir-for-php-running-as-fastcgi) in Apache+php with suexec we do need a way to handle the non-existent open_basedir directive:
php_admin_value open_basedir "/var/www/users/test"

as php is not running as module.

Any solution for that?