PHP 5.3 Deprecation Detector

With the release of PHP 5.2.14, the PHP 5.2.x branch has reached end of support. Also many people in the community pushing very hard for php 5.3 adoption. In a perfect world, this would be a very easy process and all of your code would just roll over and work perfectly. This however is nothing like a perfect world. If you use a managed hosting solution, many are still requiring you use php 5.1 since it is the latest stable release in the RHEL repositories. And if that wasn’t enough, php 5.3 deprecates a decent bit of functionality that many older scripts rely on. You can find a list of deprecated functionality in the php manual.

The functionality still works in PHP 5.3, however it now throws a E_DEPRECATED error. I’ve read many blogs who say the best fix for this is to use the following:

// WRONG !!!!!
error_reporting(E_ALL &~ E_DEPRECATED);

Sadly, this code only hides the messages from you and does nothing to actually help solve the problem. This would be like telling someone who is being chased by your favorite movie monster to just close their eyes and they won’t get hacked to bits! That doesn’t work in the movies, and I assure you it doesn’t work in your production environment either. Luckily while the monster is hacking your friend to bits you can quickly put in place a solution that will help you fix these problems using only MySQL and standard PHP functionality.

PHP lets you actually override the default handling of a specific error type using set_error_handler. By attaching a function that can quickly catch these errors and log them you can easily monitor the progress of your migration without rewriting any of your current code and without the risk of alerting your site users. It is important to note that while some argue that using the @ php operator to silence your functions is one of the worst possible things you can do, in this case I believe that should this function fail, your actual code should not. Because of this, the function does not return anything on success or error since this should be invisible to all of your application code.

First we need a database table. I use mysql because the ON DUPLICATE KEY functionality makes logging very simple. You will notice that the primary key of this table consists of 3 fields. First FileID is a md5 of the full path name of the file. The reason I use the md5 rather than the full path, is that is can be very difficult to index keys where the length may be over 255 characters. Using the md5 gives us a simple 32 character key that we can reliably use. Using the Line number and the error code, we can get a reasonable unique key for what we are doing. The Started field will let you know when the first occurred. The Last field is updated on duplicate key to let you know the last time this error occurred. The Total field lets you see how many times this error has occurred so you can see if there is a particular error that is happening frequently. It is also important to note, that all of your database users should have INSERT and UPDATE permissions to this table in a database that is not part of another application.

CREATE TABLE IF NOT EXISTS `Deprecated` (
  `FileID` char(32) NOT NULL,
  `Line` int(10) unsigned NOT NULL,
  `ErrorNo` int(10) unsigned NOT NULL,
  `Started` datetime NOT NULL,
  `Last` datetime NOT NULL,
  `Total` bigint(20) NOT NULL,
  `FileName` text NOT NULL,
  `Message` text,
  UNIQUE KEY `FileID` (`FileID`,`Line`,`ErrorNo`)
) ENGINE=InnoDB;

Now that we have our database table in place, it’s time to hook up our function. You should place this in a file that everyone on the server has at least read access to. I would also suggest wrapping the function in a quick test to make sure E_DEPRECATED is defined, so you don’t run into any troubles if this code is seen by non php 5.3 code. All error handling functions must accept 5 parameters. There is no need for error checking since this is a low level function of PHP, if PHP is sending you the wrong data, then you have bigger problems. Once you are inside the function, I use an isset check for a reasonably unique $_GLOBALS variable that holds a small custom mysql connection resource. This again is a point of debate from some developers that you should NEVER use the $_GLOBALS variable, however in this case we need to be sure that our database query does not spill into the current application database. The rest of the function is pretty straight forward: prepare our data, and run our query. That’s it. This function is meant to be a tool to help you troubleshoot where your deprecation errors are, and help you fix them. This function is not meant to fix these errors for you, or handle these errors for your individual applications.

 if (defined('E_DEPRECATED'))
 {
    function HandleDeprecated($ErrNo, $ErrStr, $ErrFile, $ErrLine, $ErrContext)
    {

       if (!isset($_GLOBALS['_php53depres'])) {
          $_GLOBALS['_php53depres'] = @mysql_connect('HOST', 'LOGIN', 'PASS');

          if (!$_GLOBALS['_php53depres'])
             return;

          @mysql_select_db('mgh_deperrors', $_GLOBALS['_php53depres']);
       }

       $FileHash = md5($ErrFile);

       $SFileName = @mysql_escape_string($ErrFile);
       $SErrStr = @mysql_escape_string($ErrStr);

       @mysql_query("INSERT INTO `Deprecated`
       (`FileID`, `Line`, ErrorNo, Started, `Last`, 
        `Total`, `FileName`, `Message`)
       VALUES
       ('{$FileHash}', {$ErrLine}, {$ErrNo}, NOW(), 
        NOW(), 1, '{$SFileName}', '{$SErrStr}')
       ON DUPLICATE KEY UPDATE
        `Total` = `Total` + 1, `Last` = NOW()", $_GLOBALS['_php53depres']);

       return;
    } // End 'HandleDeprecated'

    set_error_handler('HandleDeprecated', E_DEPRECATED);
 }

After you have defined the function, but still inside of the E_DEPRECATED check, you must register this function as the catch function for E_DEPRECATED errors. That is all you have to do in the library piece. Once this is done, you can easily attach this to every script you run by making a small modification to your global php.ini file ( normally found in /etc/php.ini ). Look for the auto_prepend_file line and point it to your file. This will automatically add your deprecation catch function to every script that runs. It is also important to note that you must restart your web server to activate this change.

auto_prepend_file = /path/to/library/deprecate.php

Once this is done visit some of your sites and it should be very clear where you have problems, and what you will need to change. You can easily see even the most widely accepted apps on the web (looking @ you wordpress) have some work to do. But with this script and a little bit of time, you can manage the migration without having to disrupt your services.