Saturday, December 22, 2007

Hints and tips using PHP

These tips are mainly from the problems I have encountered and the ways I have found to fix them. I realise that all of these tips are available in many other
places just by using a search engine but here I've collected some of the most useful in one place, I also take no credit for the solutions but hope they work
for you too.

Many of these tips involve sending headers with PHP, by their very nature it's often very difficult to tell if they are working since this information
does not appear in the web page. I personally use the 'Live HTTP header' extension for firefox which is available from
mozdev.

This allows you to view the headers sent by the browser and server in real time and is a major help in debugging header problems. If you don't use
Firefox you can instead visit Rex Swain's HTTP viewer, a very useful website for viewing header information.

Important note about the PHP header function.

The golden rule for sending headers in PHP is that you must send them before any other content, they cannot be sent after html output has
begun otherwise you will get the 'Warning: Cannot modify header information - headers already sent by...' error from PHP.

Sending xhtml with Strict XHTML 1.1 Doctypes


If you are using a Strict XHTML 1.1 Doctype then you need to make some changes to how the data is sent. In reality most servers
are set up to send all flavours HTML with a content type of text/html.


What this means that even though a browser such as Firefox should handle
a strict XHTML as an xml document meaning that a badly formed document will completely stop the page display giving a rather ugly error, it will instead
still parse the page but treat it just like text/html content so will forgive badly formed pages.

To prevent this we need to send pages with a content type of application/xhtml+xml so Firefox and other XHTML capable browsers switch to strict mode. This is
in fact a very easy thing to achieve since every page request comes with a HTTP_ACCEPT header so this tells us whether we are able to send as application/xhtml+xml
or plain old text/html.



I should point out at this time that Internet Explorer 6 will not accept a content type of application/xhtml+xml, although this may have been rectified by the time IE7
hopefully comes out in the summer.

Update - it seems according to this IE7 blog entry that IE7
will NOT accept a Content-Type of application/xhtml+xml, so the code shown here will be needed for a while longer. If you do send a Content-Type
of application/xhtml+xml to IE then you'll just get a save file dialog opening since IE doesn't know that to do with the file so asks if you want
to save it.



The php required for this is very simple indeed, we simple test for the prescence of the string "application/xhtml+xml" in the server variable HTTP_ACCEPT.

if (stristr($_SERVER['HTTP_ACCEPT'], "application/xhtml+xml"))
header("Content-Type: application/xhtml+xml; charset=utf-8");


To also send the correct content type to the W3C validator we need to test the user agent string for the value "W3C_Validator". This is
because the W3C validator does not set the $_SERVER['HTTP_ACCEPT'] string. We do this with the simple code below.

elseif (stristr($_SERVER["HTTP_USER_AGENT"],"W3C_Validator"))
header("Content-Type: application/xhtml+xml; charset=utf-8");


Headers to prevent page caching


I also had a number of problems with Internet Explorer caching dynamic content. I first noticed the problem with the menus for this website, as the menu is created with PHP
I add an opacity ( or filter in Internet Explorer ) so the current pages menu appears semi-transparent making it clear when navigating the menus which page you are currently visiting.

The problem was that with Internet Explorer this effect did not work due to the caching of the content, after some searching on Google this set of headers sent with PHP solved the
page caching problem in Internet Explorer. For me this was never an issue with Firefox so you might want to only send this header to Internet Explorer but it doesn't do any harm if sent to other browsers.

header("Expires: Tue, 01 Jan 1981 01:00:00 GMT");
header("Cache-Control: no-cache");
header("Cache-Control: post-check=0,pre-check=0");
header("Cache-Control: max-age=0");
header("Pragma: no-cache");


Custom error pages with php


On this site I use a custom 404 error page written in PHP. All you need to do to get this to work is go to the control panel of your web host and there should be an
option concerning custom error pages, just put the path to your page in the entry for 404 and save your changes.


One thing to note about doing this on IIS 6.0 or Apache is that the responsibilty of sending the correct header is handed over to your script, the
web server will NOT send the appropriate (Status: 404 Not Found) header for you. To do this with PHP simply add the following line to the top of
your 404 page.


If you do not do this then even though any human visitors will see your 404 page any search engine will get a 200 OK response by default and so it will think it has
found a valid page.

header("Status: 404 Not Found");



The same should be done if you use custom 403 pages ( forbidden ) but send the following header instead.

header("Status: 403 Forbidden");


If you want to send the equivalent header using Apache just use the following

header("HTTP/1.0 404 Not Found");


or

header("HTTP/1.0 403 Forbidden");



On Apache it's very easy to just upload a .htaccess file to the root of your server and put the path to your Error Document in there.
For details on this and how to redirect visitors using a custom 404 error page using PHP on Apache see the
Redirection using PHP and 404 error pages page.

Sessions and header("Location: ") function


I also had a bit of a nightmare with changes to a session variable not being saved. I was using it to moniter a logged in user and wanted to unset it after I logged out. The
logging out process first unsets the session and then immediately redirects the user using the header() function. However many times the session would not be unset
so I was unable to logout. My inititial code was something like this.

unset($_SESSION['login']);
header("Location: login");



The login page would then check if the user was logged in through the session and show different content based on the result. However it appeared the the session was never unset because if
you immediately use header("Location: somewhere") after unsetting a variable then PHP does not write the changes to the session database resulting in the changes not being saved.
The fix is very simple, after unsetting ( or any change to a session variable ) the session, and before the location header, you must close the session with this function.

session_write_close();


Compressing content


I only discovered how easy this is very recently. If you're using PHP 4.3 or later on IIS 6.0 then the compression module is already build in and ready to use.
First you need to add the following lines to the very start of your output.

ob_start();
ob_start('ob_gzhandler');



The first ob_start() starts output buffering while the second does the same while using the function 'ob_gzhandler' as the output handler. All this means is that after
PHP has finished compiloing the page it will sent the buffer to this function which will take care of the compression for us. At the very end of your page you need to
add the following code.

ob_end_flush();
header('Content-Length: '.ob_get_length());
ob_end_flush();



This code first outputs the buffer and then gets the buffer length which it adds as a header so the browser knows how much data is coming. The second flush is because
we need to calculte the content length and send this as a header, but we cannot send any data to the browser until all headers are sent so this allows us to get the
content length, send the header and then finally send the data to the browser. Basically were just emptying one buffer into a second buffer to allow us to send the
header.

IIS Extension mappings settings


If you are having problems with getting a custom 404 page to work with PHP it may have something to do with the extension mapping within IIS. When I tried to implement
a custom error page I kept getting the text "No input file specified" returned with nothing else when viewing a non-existant page. This turned out to be because PHP
was not checking to see if the file actually existed before trying to parse it so any page with a extension it attempted to parse. As a result it tried to parse
the page and reutned the generic error above. This was fixed by my web host checking the 'Check that file exists' checkbox in the extension mappings for PHP.


To find this setting, open up the IIS snap in, go to the properties of your website and select the 'Home directory' tab. Then click on 'Configuration' in the bottom
part of the window below where it says 'Application settings'. This opens up the 'Application configuration' window, select the entry for PHP in the list box at the bottom
and click 'Edit' to open the window shown below. This path summarised here:


Website Properties > Home directory > Configuration > Highlight PHP line > Edit > Check that file exists


Make sure the check box highlighted in red is checked and save your settings. Now when you visit a page that doesn't
exist on you server PHP will check to see if it's there and if not will trigger the 404 response correctly. If you do not have access to the server, as was my case,
if you log a support ticket they should be able to enable it for you.

No comments: