Implementing Namespaces in PHP 5.6
You may have heard of PHP namespaces being introduced in PHP 5.3, and subsequently updated. One purpose being to prevent naming conflicts between different codebases by having each class/function assigned to a particular namespace.
In other words, it lets you name your functions and classes without having to worry about someone else having used the same names in the same scope when you introduce third-party code or a plugin.
As with everything that sounds simple, there's actually a lot involved, and it's not something that you can implement piecemeal.
Why Impement Namespaces
For us the goal was to do something about our 'helper' functions - a motley collection of extremely useful functions not belonging to any class, but used by a number of different classes.
For example we might have the following:
<PHP
include "myFunctions.php"; // defines function doSomething() { ... }
doSomething();
?>
The problem here is that 'doSomething' now sits by default in the global namespace. In fact everything we do pollutes the global namespace unless we implement our own namespaces.
If we now include other code - an RSS parser, Twitter plugin, PDF generator, etc - we have to be sure they they don't also define 'doSomething' as that could lead to some hard to track errors.
The same applies to any classes we define. It's easy to see how any number of classes could end up with the same or similar names.
Before namespaces we might have used a 'static' class, creating function calls such as 'MyToolbox::doSomething()', but these would still reside in the global namespace.
Getting Started
Be warned that once you start you will need to follow the process through to the end. Once you start using namespaces it quickly affects your entire codebase.
The first step is to choose a sensible namespace. It should be something distinct relating to your domain name, company, full name or something similar, and not a generic term like 'Database'.
For example we might use:
- namespace Chirp;
- namespace ChirpInternet;
- namespace AuComChirp;
- namespace ChirpComAu;
Let's say that we've chosen 'Chirp' based on our company name. It's a bit shorter than ideal, but easy to type and not obviously linked to another codebase.
Now we move our helper functions into the new namespace by adding a namespace line at the top (it must be at the top) of the file:
Chirp/toolbox.php
<?PHP
namespace Chirp;
function doSomething()
{
...
}
?>
Simple enough, so far.
Cascading Effects
The consequence of this is that 'doSomething' no longer exists in the global namespace, but in our own private namespace, so the code snippet from before becomes:
<PHP
include "Chirp/toolbox.php";
\Chirp\doSomething();
?>
But we don't want to have to prefix everything with \Chirp\ so we need to move this script into the same namespace:
<PHP
namespace Chirp;
require "Chirp/toolbox.php";
doSomething();
?>
Once we've defined the current namespace at the top of the file the rest of our code remains unchanged, or so you would think.
The first thing to realise is that any 'included' or 'required' files also need to define the namespace as it's not inherited from the parent script. i.e. if you include 'header.php' and 'footer.php' then they both need to start with 'namespace Chirp;' in order to use our helper functions without a prefix.
And each and every class we've authored also needs to be moved into the same 'Chirp' namespace in order to access our functions.
Autoloading Classes
By now you should have already implemented an autoloader if you're using your own PHP classes.
For example:
<?PHP
// load 'Class' from PHP_INCLUDE_PATH/classes/class.php
spl_autoload_register(function($class) {
$classfile = 'classes' . DIRECTORY_SEPARATOR . strtolower($class) . '.php';
if(FALSE === stream_resolve_include_path($classfile)) return;
include $classfile;
});
?>
Without going into detail, this will cause an instantiation of the 'SecureToken' class to auto-include ~/classes/securetoken.php containing the class definition.
Now that we're using a namespace, however, we need some changes. Rather than receiving a value of 'SecureToken' our function now receives 'Chirp\\SecureToken' (and further if you use subnamespaces).
The new version looks something like:
<?PHP
// autoload 'Class' from ~/classes/class.php and '\Chirp\Class' from ~/Chirp/class.php
spl_autoload_register(function($class) {
$parts = explode('\\', $class);
if(count($parts) > 1) {
$classfile = array_shift($parts) . DIRECTORY_SEPARATOR . strtolower(implode(DIRECTORY_SEPARATOR, $parts)) . ".php";
} else {
$classfile = 'classes' . DIRECTORY_SEPARATOR . strtolower($class) . '.php';
}
if(FALSE === stream_resolve_include_path($classfile)) return;
include $classfile;
});
?>
So we need to create a new directory for our PHP class files, moving them from PHP_INCLUDE_PATH/classes/* to PHP_INCLUDE_PATH/Chirp/*. And similarly for any other namespaces - the directory structure matches the namespaces.
We put this autoload register call into our toolbox.php file so it sits alongside the \Chirp class files.
As you can see we've left intact the old behaviour for loading non-namespaced classes from a common directory. Many/most publically available PHP classes have not yet implemented namespaces, and maybe never will.
Here you might want to read up on the PSR-4 autoloader specification which aims to create a community standard.
Built-in and third-party classes
At this stage you might think you're finished, but the fun is just beginning. What we've achieved so far is moving all our our code, functions and classes into the 'Chirp' namespace.
Every time we call a function PHP will look first in our namespace, and then walk back up to the global namespace until the function is found. So we can even overwrite/overload global functions if we want (or don't want).
With classes, however, PHP will only look in the current namespace and not elsewhere. That means we have to take extra steps if our code uses any built-in, global, or foreign classes.
Some of the built-in classes we're talking about are:
- the Exception family;
- Standard PHP Library (SPL) classes;
- other PHP classes (DOMDocument, DateTime, ZipArchive, ...); and
- the stdClass object.
Again, there are multiple approaches you can use. The simplest being:
<?PHP
namespace Chirp;
$object = new \stdClass;
?>
or:
<?PHP
namespace Chirp;
use \stdClass;
$object = new stdClass;
?>
Even if you're not using namespaces, you should get in the habit of prefixing all reference to PHP built in classes with '\'. The most common being \Exception.
The same applies when using third-party classes. Either they are in the global namespace:
<?PHP
namespace Chirp;
use \RSSWriter;
$rss = new RSSWriter(
...
);
?>
or come with their own namespace:
<?PHP
namespace Chirp;
use \Facebook\FacebookRequest;
$request = new FacebookRequest(
...
);
?>
In this case all the \Facebook classes and functions will run in the \Facebook namespace and won't/can't access any of the \Chirp functions or classes.
If a function/class from another namespace has the same name as one of your own then you can either only refer to it using the full path, or via an alias:
<?PHP
namespace Chirp; // includes a 'Widget' class
use \ACME\Widget as ACMEWidget;
$widget1 = new Widget();
$widget2 = new ACMEWidget();
?>
Other Gotchas
If you thought that was then end of it, you'd be mistaken. You will find other hard to predict situations where your code needs updating.
Callback functions
One notable example being callback functions (used in array_walk, array_map, usort, uasort, etc.)
This means that instead of:
<?PHP
namespace Chirp;
function cleanData(&$str)
{
...
}
array_walk($array, 'cleanData');
?>
you will need to specify the full path to the callback function including the namespace:
array_walk($array, '\Chirp\cleanData');
or better:
array_walk($array, __NAMESPACE__ . '\cleanData');
Function exists
Similarly, when checking if a function exists, instead of:
<?PHP
namespace Chirp;
if(!function_exists('doSomething')) {
...
}
?>
The function, which exists in the global namespace, needs to know in which namespace to look:
if(!function_exists(__NAMESPACE__ . '\doSomething')) {
...
}
Basically every time you refer to a function by name as a string you will need to make changes to your code. You will find out if you've missed any when you see error.log entries like:
PHP Warning: array_walk() expects parameter 2 to be a valid callback
Constructors
Not exactly namespace related, but relevant all the same. Without namespaces you can name your constructor after the class itself:
<?PHP
class dbTable
{
function dbTable()
{
...
}
}
?>
But with namespaces your constructor method to be renamed to __construct otherwise the object will be instantiated without it being called, and with no error or warning!
<?PHP
namespace Chirp;
class dbTable
{
public function __construct() {
...
}
}
?>
If you've come across any other examples of working pre-namespace PHP code needing attention let us know using the Feedback form below.
References
- PHP.net: Namespaces
- PHP.net: spl_autoload_register
- PSR-4: Autoloader
Abu 28 August, 2016
It would be most fed-up comment ...
What a mess, i will rename my helper functions with a prefix and search/replace in all code.