Introduction
In this article, we'll explain why and how we implement PHP namespaces in our web application projects. As nearly all of them are based
on it, the implementation is done in the Scavix Web Development Framework,
so you will find the complete code over there. If you are not familiar with it you may want to read our other articles first:
Audience
Experienced software developers with solid PHP knowledge.
The problem
You probably experienced it yourself: You're including a third party library into a project and it uses
some classnames you're already using somewhere in your project (common names like 'Logger' or 'Model').
This is bad if you are not able to change your classnames because of...
- ... backwards compatibility.
- ... external class references in a database.
- ...
It turns even worse if you can't change the third party library because it's i.e. closed source (or for whatever reason).
In such a case you wouldn't be able to use this library then.
The solution
The short version: Use namespaces in your code.
In more detail: If you encapsulate your code into namespaces you may use any other library that doesn't use the same namespace.
Of course such a conflict is much more unlikely and if you choose your namespace wisely (like CompanyName\ProductName) it will never occur.
The problems with the solution
As always, there are some obstacles to take before you have clean namespacing.
There are none if you start from zero with a new project, but remember: if you had a conflict with a third party library so you're working
on an existing project.
If you add namespaces to your files, you need to do it for the whole project. It will crash until you are done with it.
That is of course not nice and you will probably forget some places so the namespacing redesign will take you quite a while. But it's worth it, really.
Solving problems with the solution
In short: Be backwards compatible. Of course it's not that simple. Oh wait: In fact in this case it is!
Oh wait again: it's only if you are using autoloading, but as we're sure you do we will just continue :)
The basic procedure is to create a class alias if the requested class was plain but the found class has a qualified name.
Sounds cool, eh? Let's see in this example:
namespace Scavix\SomeCoolProject;
class MyClass { }
This is the standard autoloader mechanism for classnames:
function system_spl_autoload($class_name)
{
$file = __search_file_for_class($class_name);
require_once($file);
}
spl_autoload_register("system_spl_autoload",true,true);
References: __search_file_for_class,
spl_autoload_register.
require_once("autoloader.php");
$mc = new MyClass();
$mc = new Scavix\SomeCoolProject\MyClass();
Of course the 'class not found' exception is expected in this example. So to make that code work we will now extend the autoloader
to implement that rule from above automatically: Create a class alias if the requested class was plain but the found class has a qualified name.
function system_spl_autoload($class_name)
{
if( strpos($class_name, '\\') !== false )
{
$orig = $class_name;
$class_name = array_pop(explode('\\',$class_name));
}
$file = __search_file_for_class($class_name);
$pre = get_declared_classes();
require_once($file);
$post = array_unique(array_diff(get_declared_classes(), $pre));
foreach( $post as $cd )
{
$d = explode('\\',$cd);
if( count($d) > 1 )
{
create_class_alias($cd,array_pop($d));
}
}
$def = array_pop($post);
if( !isset($orig) && !$def )
{
foreach( array_reverse($pre) as $c )
{
if( !ends_with($c,$class_name) )
continue;
create_class_alias($c,$class_name,true);
break;
}
}
else
{
$class_name = isset($orig)?$orig:$class_name;
if( strtolower($def) != strtolower($class_name) && ends_iwith($def,$class_name) )
{
create_class_alias($def,$class_name,true);
}
}
}
spl_autoload_register("system_spl_autoload",true,true);
function create_class_alias($original,$alias,$strong=false)
{
if( $strong )
class_alias($original,$alias);
$alias = strtolower($alias);
if( isset($GLOBALS['system_class_alias'][$alias]) )
{
if( $GLOBALS['system_class_alias'][$alias] == $original )
return;
if( !is_array($GLOBALS['system_class_alias'][$alias]) )
$GLOBALS['system_class_alias'][$alias] = array($GLOBALS['system_class_alias'][$alias]);
$GLOBALS['system_class_alias'][$alias][] = $original;
}
else
$GLOBALS['system_class_alias'][$alias] = $original;
}
References: __search_file_for_class,
spl_autoload_register,
get_declared_classes,
ends_with,
ends_iwith,
class_alias.
Okay: Some more lines than the original one, but not too complicated, right? ;). Now this will work just fine:
require_once("autoloader_ex.php");
$mc = new MyClass();
$mc = new Scavix\SomeCoolProject\MyClass();
So now you may migrate your project part by part to use namespacing without the fear of crashes due to missing namespace declarations.
Sorry for dropping this last excuse to skip namespacing :)
Some words on namespacing
As you may have noticed: We like namespacing! But you will have to do it the right way and think about where it's useful. So we follow these rules in our projects:
- Use a unique namespace root
- Only use ONE namespace per file
- Ensure that each class/interface is in a namespace
- Put each class/interface in it's own file and don't put anything else in there
- don't add functions to namespaces
Change-log
- 2013/08/26: Initial publishing