PHP Women Best Practice Contest Posts

I made a few posts to the PHPWomen.org Article Competition and felt I should share them here.

I wrote three entries:

  1. Flash Your Errors: It's Illegal In 28 States! on combining flash messages and error reporting
  2. David Copperfield Wasn't This __magical: __autoload() is Awesome! on using the __autoload functions and PEAR style namespacing
  3. Path Secrets Of The Most Awesome on making your path operations indestructible across operating systems.

Flash Your Errors: It's Illegal In 28 States!

[Original]

I've often had problems in sites where I bounce requests around with losing error messages and having to trudge through the system error log looking for what exactly went wrong in my code.

For example, say for instance you submit a form to PageA.php. PageA.php, after crunching the POST data, decides to header('Location: PageB.php') redirect you to PageB.php. HOWEVER, PageA.php threw an error that did not interfere with the header() call that caused a bug that you are having a hard time tracing.

Here is where I bring up the concept of flash messages. Anyone familiar with CakePHP, Symfony, or the Zend Framework (and others) will be somewhat familiar with flash messages.

A flash message is a message (sometimes a string, in some cases just a variable store) that persists until it is read. Meaning if a page sets a flash message, the message will persist across page loads until the message is retrieved.

A more concrete example is a little flash messenger I wrote for myself:

<?php
class FlashMessenger
{
	protected function __construct() { }
 
	/**
	 * Adds a message to the flash message queue. Message can be of mixed type
	 *
	 * @param mixed $message
	 */
	public static function addMessage($message)
	{
		if( !isset($_SESSION) )
		{
			session_start();
		}
 
		if( isset($_SERVER['REQUEST_URI']) )
			$uri = $_SERVER['REQUEST_URI'];
		else
			$uri = __FILE__;
 
		$_SESSION['__lat_flashmessages'][] = array(
			'URI' => $uri,
			'message' => $message
		);
	}
 
	/**
	 * Get stored messages from queue
	 *
	 * @return Array
	 */
	public static function getMessages()
	{
		if( !isset($_SESSION) )
		{
			session_start();
		}
 
		if( isset($_SESSION['__lat_flashmessages']) && is_array($_SESSION['__lat_flashmessages']) )
		{
			$messages = $_SESSION['__lat_flashmessages'];
			unset($_SESSION['__lat_flashmessages']);
		}
		else
		{
			$messages = array();
		}
 
		return $messages;
	}
}
?>

You'll notice I store the messages in the session until I retrieve them (where I unset them as they have been read).

To solve our original problem, we use a flash messenger to store errors until we can display them. Meaning PageA.php sets the error in a flash message and PageB.php loads the message and displays it! The easiest way to do this overloading our Error and Exception handlers in some common location shared by every script (say a common.php or index.php):

<?php
function appErrorHandler($errno, $errstr, $errfile = "", $errline = -1, $errcontext = array())
{
	FlashMessenger::addMessage(array(
		'errno' => $errno,
		'errstr' => $errstr,
		'errfile' => $errfile,
		'errline' => $errline,
		'errcontext' => $errcontext
	));
}
 
function appExceptionHandler(Exception $exception)
{
	FlashMessenger::addMessage(array(
		'exception' => $exception,
	));
}
 
set_error_handler( "appErrorHandler" );
set_exception_handler( "appExceptionHandler" );
?>

Now all we'd have to do is perform a FlashMessenger::getMessages(); on each page (obviously after any header() calls or it defeats the purpose) and either var_dump() it or format it to check out the errors.

Now when our code triggers an error but still successfully redirects, we can see the errors on the final page! (And with my class storing the REQUEST_URI of the page that adds the flash message, you'll easily be able to tell which page triggered what error should both PageA.php and PageB.php have issues!

Much thanks to my friend Chris Weldon for this idea.

David Copperfield Wasn't This __magical: __autoload() is Awesome!

[Original]

Is it possible that I could get any cornier with my post titles?

For anyone developing a moderately-to-highly object oriented program (as we should all be doing), balancing the need to keep KISS by separating classes/interfaces into their own files with having to make sure those files have been loaded properly can be tedious at best.

While you could take a scorched earth policy and just load up every file, does every execution (page load in web terms) require the same classes and are we wasting processing overhead?

As with most things, PHP already has a native, built-in solution waiting for us: The magic function __autoload().

When PHP 5.x comes across a reference to a class that's not loaded (say new Class(); or Class::CONST), it actually passes the offending class name to a user-defined function called __autoload() which is supposed to attempt to find and include() the class.

Now, the more perceptive readers will ask themselves "what about applications where I'm using third party libraries like Doctrine or Dwoo that might possibly use __autoload(), what happens to mine?" The SPL comes to the rescue in the form of spl_autoload_register().

You can use spl_autoload_register() to add your function to a universal __autoload(), managed by the SPL of course.

Now when you decide to use __autoload(), you will want to optimize how your application is laid out as you shouldn't waste cycles searching for a file (defeats the purpose of __autoload()), rather you should have a uniform name-to-path mapping.

The easiest (and best, IMHO) method is when using PEAR/PHP style namespacing (Class_Names_Like_This) is to just put each namespace in it's own directory (Class/Names/Like/This). The final namespace is the filename (This.php) and therefore Class_Names_Like_This should be stored in ~/Class/Names/Like/This.php.

Now, when working with third party libraries (or even purely my own code), I prefer to prefix all my classes with their own namespace. For example, if I was writing an application I wanted to call Foo, all my classes would begin with 'Foo_'. This helps out in (A) making sure if I include third party code none of the classes will conflict with mine, and (B) it gives me an easy way to make sure my __autoload() functions do not try and process anything but my own classes!

So, time for some code examples: Lets begin assuming we are calling our app "Foo" and we have already setup our root path to Foo as FOO_ROOT.

<?php
function foo_autoload($classname)
{
	$pieces = explode('_', strtolower($classname));
 
	//If the classname doesn't begin with Foo then exit this function
	if( !$pieces || $pieces[0] !== "foo" )
		return;
 
	$file = array_pop($pieces) . ".php";
	$path = implode(DIRECTORY_SEPARATOR, $pieces);
 
	//If $path isn't empty add a trailing DIRECTORY_SEPARATOR
	if( $path !== "" ) $path .= DIRECTORY_SEPARATOR;
 
	$full_path = FOO_PATH . DIRECTORY_SEPARATOR . $path . $file;
 
	if( file_exists( $full_path ) )
	{
		require_once($full_path);
	}
}
 
spl_autoload_register('foo_autoload');
?>

Notice I named my __autoload() function foo_autoload(). I did this because spl_autoload_register() handles the actually __autoload(), so I give it a name to prevent any naming conflicts.

I lowercased the class name in the explode function to make sure the file names on the disk are consistent even in their casing. I don't like to risk the possibility that my script will run on a server with strict file name casing and be SOL.

Another thing to notice is that I return null when either there's a problem with $pieces or the class isn't prefixed with Foo_. Returning from the function allows the SPL to move on to the next __autoload() function (if it has one).

And finally you'll notice I actually check for the existence of the file before I attempt to require the file. I pick the require because obviously we've referenced the class (as we are in an __autoload() function), but I check the existence of the file to either give any other __autoloads() another chance to find the particular class. Or, if I so choose, I can put in error handling into my __autoload() function to be more explicit to the user that the particular file cannot be found.

A word of warning, with 5.3 and name spaces on the horizon, this code will only work with the old school method of PHP name spacing. From what I've gathered you can just explode on a '::' as the potential name space for a class is passed as a string, however I'm not sure on this.

Path Secrets Of The Most Awesome

[Original]

Our friends require() and include() rarely break but when they do, boy do they go up in fireworks. Most people don't know how to avoid the fireworks because include() is such a basic construct, no one ever thinks about what is going on beyond "paste the code from this file here and execute".

Problems begin to arise when flipping code between Linux and Windows environments (while Linux uses the smarter, easier to type /, Windows likes to use the \, forcing you to escape it, as \ has special meaning in PHP). Usually PHP handles these problems if you use relative paths, however some people like their full paths. I personally have an irrational fear that PHP will not understand include('/full/path/' . '../file.php'); is supposed to grab /full/file.php.

Even worse, what if you run into an issue where your shared host b0rks PHP's include_path variable so it doesn't search directories properly?

What I, and many others, tend to do to make their applications and scripts as portable as possible is to exploit PHP's magic constants, predefined constants, and dirname.

PHP has a predefined constant, DIRECTORY_SEPARATOR, that is automagically set to either a / or \ based on the host operating system.

PHP also has nice magic functions it sets based on where its executing in code. The example we care about is __FILE__, which is set to the /full/path/to/the.file currently being executed.

If /path/to/index.php includes /path/to/include/include.php, any reference to __FILE__ in index.php will be "/path/to/index.php" and any reference to __FILE__ in include.php will be "/path/to/include/include.php".

dirname() simply pops the full directory off a path. If your path includes a file name, it returns the path to the file. If the path is a directory, it returns the directory containing said directory. In simpler words, dirname() always does a `cd ../`.

For example, in the bootstrap of my applications I like to do this:

<?php
// Full path to this file, _no_ trailing slash
define( 'APP_ROOT', dirname(__FILE__) );
 
// Equivalent to running `cd ../` in dirname(__FILE__)
define( 'ONE_LEVEL_UP', dirname(dirname(__FILE__)) );
 
// Directory separator is automatically set to either / or \ based on host OS
define( 'INCLUDE_DIR', APP_ROOT . DIRECTORY_SEPARATOR . 'include' );
?>

This helps me keep my urls clean and not dependent on anything.

Usage of dirname() and DIRECTORY_SEPARATOR (and it's twin, PATH_SEPARATOR, used for set_include_path()) also extends to using __autoload() functions (replace all _'s with DIRECTORY_SEPARATOR's and you have a nice auto include path generation).

One final note, if you don't know already, you are probably best off using require_once() for all of your includes. I use it because PHP doesn't whine at me if I try to include a file twice (it just ignores the new require_once() calls) and when a file doesn't exist or can't be access, PHP drops everything and dies (e.g. it lets me know real quick like somethings wrong with a file, makes life easier when it comes to debugging).

4 Comments

  1. tata:

    Erm… because I am a deeply thoughtful person, I am leaving you this comment. Because I have no idea WTH this is all about, I’m only saying that you sound really smart! Be well and see you on Plurk!

  2. Daniel Cousineau’s Blog: PHP Women Best Practice Contest Posts | Development Blog With Code Updates : Developercast.com:

    [...] Cousineau posted copies of his submissions to the PHP Women group’s Article Contest (Best Practices) that finishes up [...]

  3. Daniel Convissor:

    You’re making a mountain out of a mole hill regarding paths. I’ve been developing PHP applications on Windows boxes and deploying them on *nix servers for ten years. I only use “/” to separate directories. PHP handles it perfectly.

  4. queefsnoggle:

    I really only use flash messages to tell the user when they did something wrong/right/unexpected, not for debugging. For debugging I just exit before the redirect line while I get things working and once they’re working I move the exit to the line after the redirect.

Leave a comment