PHP project and library structure tips

I thought I’d share some tips on working on larger projects, and how to name and structure stuff.  Most importantly, whatever conventions you come up with, they should be as simple as possible, but no simpler.  If these conventions and structures are too complicated, no one (not even yourself) will adhere to them and everything will revert/degrade into one big mess in a heart beat.  However, they shouldn’t be too simplistic (constrictive) to keep you from extending them (ie. rules like: only one depth of namespaces, only 20 characters for class names.)

Basic Structure

Create a directory outside of your web servers document root for all your libraries. That way you don’t need “guard index.html”-files or “.htaccess”-files to restrict access to these directories. It prevents malicious visitors from accessing them and observing possible side effects (though there ideally wouldn’t be any ;-). My suggestions would be /var/local/php for generic libraries and /var/local/<project> for project specific libraries. Of course this only applies to *nix-like systems, essentially just use whatever makes sense to you and your team.

Namespaces

Use them!  They allow you to describe sets of functionality, purpose, origin or project (imperative here is “allow to group” ;-).  Pre PHP 5.3.0 (no namespace support) you can simulate that by prefixing your classes, functions and constants with the namespace name using  underscores “_” as separators (Inventory_Item, Inventory_Category, etc.)  For each namespace create a directory inside your library directory and name it exactly like the namespace (capitalisation and everything), this will save you lots of name mangling trouble later.

Classes, Functions and Constants

Put every class in its own file and name that file exactly like the class (capitalisation) but omit the namespaces.  Put that file in the directory of the namespace(s) of the class.  This will again save you from name mangling later on to resolve the corresponding file for a class.

Try and group unbound functions (functions not belonging to classes) into classes as “public static”-functions. While doing this,  try and avoid “util”, “misc”-classes, these will encourage sloppyness!  Maybe you will even find that these functions share some state (you previously passed by argument) and could be refactored into regular methods instead.

Prefer class constants over defines, since defines end up in the global namespace. If it can’t be avoided to use a define, at least prefix them with the appropriate namespace, class or function name.

Including Classes

Starting with PHP 5.1.2 an autoloading mechanism was introduced. You should definitely use autoloading over manually including class files (and if you do need to include a file you should use “require_once” where possible.)  I’m just going to assume you know about how autoloading works and will only explain how the way everything was structured begins to pay off.

Now, inside your library directory you should only have other directories to your libraries. Inside each library directory create  a new  PHP file “Loader.php” with the following code and replace <LIBRARY_NAME> with the library name:

<?php
/**
 * Static initialization and class loading.
 */
final class <LIBRARY_NAME>_Loader {
	/**
	 * Only load classes with this prefix.
	 */
	const NS = '<LIBRARY_NAME>';
	/**
	 * Absolute path to base directory containing library
	 * @var string
	 */
	private static $directory;
	/**
	 * Loads $class if it begins with library namespace.
	 * @param string $class
	 */
	public static function autoload( $class ) {
		if ( strpos( $class, self::NS . '_' ) === 0 ) {
			$fileName = str_replace( '_', '/', $class );
			include self::$directory . "/{$fileName}.php";
		}
	}
	/**
	 * Register the autoload function.
	 */
	public static function register() {
		$loaderName = __CLASS__ . '::autoload';
		// casting to array, can return false
		$autoloaders = (array) spl_autoload_functions();
		if ( !in_array( $loaderName, $autoloaders ) ) {
			self::$directory = dirname( __FILE__ );
			spl_autoload_register( $loaderName );
		}
	}
}
<LIBRARY_NAME>_Loader::register();

All that is now left to do, is to set PHP’s include path to your library dir, and include the loader file of each library you want to use:

<?php
$include_path = explode( PATH_SEPARATOR, get_include_path() );
array_unshift( $include_path, '<YOUR_LIBRARIES_DIR>' );
set_include_path( implode( PATH_SEPARATOR, $include_path ) );

require_once 'Inventory/Loader.php';

$cat = new Inventory_Category( 'Appliances' );
$item = new Inventory_Item( 'Blender' );
$cat->addItem( $item );
$cat->saveToDb();

Everything else will be autoloaded. If one of the libraries depends on another, you should include the loader file of the required library inside the library’s loader file:

<?php
require './Db/Loader.php';

final class Inventory_Loader {
//...
}

Summary

In my experience, this is simple enough to be understood and adhered to by myself and others I have worked with, yet still flexible enough to be adapted to every type and size of project I have encountered. It is also easily documented and explained to new members of your team. This structure also plays nice with all source control systems and IDEs I have worked with. There are, of course, many other ways to structure your projects and this is just how I do it ;-)

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: