Apache, MultiViews and PHP

[auf Deutsch]

Prerequisites

Symptoms

You may have noticed one of the following:

The problem

The installation documents for PHP currently advise you to add the following line to Apache’s httpd.conf file:

AddType application/x-httpd-php .php .phtml

This tells the server to treat .php and .phtml files as being of media type (aka MIME type) application/x-httpd-php. The PHP module, loaded in a LoadModule directive earlier in the file, tells the server that it handles files of this type when it loads up.

Without MultiViews, this works fine. A request for foo.php will cause the server to pass that file to the PHP module, which will do its stuff—usually serving content as type text/html.

MultiViews will take a request for foo, and find matching files. For example, I could have an HTML file foo.html and a PDF file foo.pdf. MultiViews would determine which of the possible choices best matched the Accept header in the request, and serve the appropriate file.

A standard web browser might send an Accept header of text/html,text/plain,application/xhtml+xml. Apache knows that .html files are of type text/html and that .pdf files are of type application/pdf. MultiViews would therefore choose and serve the HTML file to the browser.

However, in the case of foo.php, the AddType line identifies .php files as type application/x-httpd-php. This does not match any of the types in the above Accept header, so Apache returns 406 Not Acceptable. MultiViews has no way of knowing that your .php file will end up being served as text/html.

The fix!

The workaround is to replace the AddType line with:

AddHandler php-script php 
AddType text/html php

for PHP 4.x, and:

AddHandler php5-script php 
AddType text/html php

for PHP-5.x. This works for me with Apache 2.0.49 and PHP 4.3.[56] and 5.0.[01]. If you want to define additional extensions and associated default types, just add additional AddHandler and AddType lines.

Caveat

PHP script output type

MultiViews will use the default type as listed in the AddType line, regardless of what the PHP script ends up outputting.

For example, pages on this site used to check for application/xhtml+xml in the Accept header your browser sends. If found, the page was sent as that type by modifying the Content-Type header, and the DOCTYPE was set to XHTML 1.1. [more information]

With the above simple fix, if a browser submitted a request with an Accept header that includes application/xhtml+xml but did not include text/html, Apache would return a 406 error because MultiViews has been told that .php files are text/html.

I solve this by defining additional extensions:

AddHandler php-script phtml pxhtml
AddType text/html phtml
AddType application/xhtml+xml pxhtml

(or php5-script as appropriate)

I then write all my files as .phtml files and create symbolic links (Windows users: think shortcuts) pointing to them with the same file name but with a .pxhtml extension:

(file)  multiviews.phtml
(link)  multiviews.pxhtml -> multiviews.phtml

MultiViews then has a choice of document types to serve, and I only need to maintain one file. This also removes the need for me to parse the Accept header: I can use PHP’s $_SERVER['SCRIPT_FILENAME'] to find which of the two files MultiViews chose.

One important consideration here is that Internet Explorer (under certain conditions) may send an Accept header of */*, even though it cannot accept application/xhtml+xml.

Fortunately, when faced with a totally ambiguous choice, Apache’s selection algorithm uses the alphabetically-first filename that satisfies the conditions. By choosing file extensions carefully, you can specify the default type to be used. In my implementation, .phtml comes first, so the text/html variant is the fallback default.

Version dependence

PHP and Apache are being continually updated, and this may not work with all versions.

I don’t believe the solution presented above works with Apache 1.3, due to the 1.3-compatible module not catching php-script. Leo Bouchet wrote to me with a solution which I haven’t verified: read his mail; and there’s another approach at Turnip’s Patch. One possible alternative solution is to use type maps: see the content negotiation and mod_negotiation documents at the Apache web site.

Images

If you leave the extensions off your images, you will still get 406 errors from certain requests.

For example, the Google crawler may follow a link to an image water-bear.jpg referenced only as water-bear. The crawler sends an Accept header that does not include any image types, so MultiViews can’t find an alternative. [more on water bears]

You could provide alternative text or HTML files for each of your images. My policy is not to worry about it.

Acknowledgements

My thanks to Alan Flavell for his help, patience and language negotiation notes page; and to Joshua Slive for providing basically the same information as Alan on Apache’s BugZilla database.

[Notes index]