PHP5: Namespaces
In version 5, PHP finally puts its toe into the namespace water.
What Are Namespaces?
If you’ve worked in other languages like C++ or Java, you know what namespaces are good for: primarily, they’re to prevent collisions between names. In PHP, like most languages, you can’t have 2 functions with the same name, or 2 classes with the same name, or 2 of anything sharing the same name, without redeclaring that object or doing some sort of overloading. (If you could, how would the compiler know which one to use?)
This is especially a problem with a codebase shared by many independent developers. Take a look at WordPress plugins, for example. If one developer writes a plugin that creates a function called ActivateFoo(), and another developer happens to create plugin that also has that function, then PHP will die with a “Cannot redeclare ActivateFoo()” message when you install both plugins.
One way around this is to try and use unique names, such as Widget_Plugin_ActivateFoo(), but this results in longer, harder-to-read code.
Namespaces alleviate this problem. A namespace is a defined space where you put your code. In fact, you can even use the names of internal PHP functions and classes, as long as you declare them in your own namespace.
Declaring Namespaces in PHP
In true PHP fashion, namespaces are a grafted-on feature to the existing language. So, the syntax and rules are a bit odd from what you would see in other languages where namespaces were built-in from the start.
First, only classes, functions, and constants are affected by namespaces. Variables are not affected or referenced by namespace, I guess because PHP does not have variable declarations like in other languages.
Namespaces are defined using the namespace keyword:
namespace MyProject; const CONNECT_OK = 1; class Connection { /* ... */ } function connect() { /* ... */ }
In the above example, the constant, class, and function are all declared in the “MyProject” namespace.
The namespace declaration must be the first code in your file — no other PHP code (other than comments) can come before it, not even HTML:
<html> <?php namespace MyProject; // fatal error - namespace must be the first statement in the script ?>
A file can contain multiple namespaces:
namespace MyProject; const CONNECT_OK = 1; class Connection { /* ... */ } function connect() { /* ... */ } namespace AnotherProject; const CONNECT_OK = 1; class Connection { /* ... */ } function connect() { /* ... */ }
This creates 2 versions of each of the objects, each version in its own namespace.
If you must put multiple namespaces in a single file, the alternate bracketed syntax is preferred:
namespace MyProject { const CONNECT_OK = 1; class Connection { /* ... */ } function connect() { /* ... */ } } namespace AnotherProject { const CONNECT_OK = 1; class Connection { /* ... */ } function connect() { /* ... */ } }
You can also put code inside the global namespace using the bracketed syntax:
namespace { // global code $a = MyProjectconnect(); echo MyProjectConnection::start(); }
But note that you can’t put code outside the namespace brackets:
namespace MyProject { const CONNECT_OK = 1; class Connection { /* ... */ } function connect() { /* ... */ } } $a = 5; /* Error: No code may exist outside of namespace {} */
PHP5 also allows you to define a hierarchy of namespaces, similar to the hierarchy of a filesystem. Maybe that’s why they chose the backslash character for the delimiter:
namespace MyProjectSubLevel; const CONNECT_OK = 1; class Connection { /* ... */ } function connect() { /* ... */ }
Referencing Namespaces
Like in filesystems, namespaces can be referenced in multiple ways:
- If you don’t specify a namespace (no prefix), PHP will assume you are referencing the current namespace (except for global fallbacks — see below). This is called unqualified.
- You can use a relative path such as “currentnamespacepathfoo()”, where the path doesn’t begin with a slash. This is called qualified.
- You can use an absolute path such as “absolutenamespacepathfoo()”, where the path begins with a slash. This is called fully qualified.
The PHP manual has good examples, but here’s a reduced version:
namespace FooBar; /* Unqualified name */ foo(); // resolves to function FooBarfoo foo::staticmethod(); // resolves to class FooBarfoo, method staticmethod echo FOO; // resolves to constant FooBarFOO /* Qualified name */ subnamespacefoo(); // resolves to function FooBarsubnamespacefoo subnamespacefoo::staticmethod(); // resolves to class FooBarsubnamespacefoo, method staticmethod echo subnamespaceFOO; // resolves to constant FooBarsubnamespaceFOO /* Fully qualified name */ FooBarfoo(); // resolves to function FooBarfoo FooBarfoo::staticmethod(); // resolves to class FooBarfoo, method staticmethod echo FooBarFOO; // resolves to constant FooBarFOO
You can also access the global namespace using an absolute reference:
namespace Foo; function strlen() {} $a = strlen('hi'); // calls PHP's built-in strlen
If you want to access namespaces in a more abstract way, you can use the __NAMESPACE__ constant:
namespace MyProject; function get($classname) { $a = __NAMESPACE__ . '\' . $classname; return new $a; }
You can also use the namespace keyword as an equivalent of “self”:
namespace MyProject; namespaceblahmine(); // calls function MyProjectblahmine()
There is an exception to the rule when referencing unqualified names, for functions and constants only (not classes): if the function/constant does not exist in the referenced namespace, PHP5 will fall back to the global namespace and use that function/constant in its place:
namespace ABC; const E_ERROR = 45; function strlen($str) {} echo E_ERROR, "n"; // prints "45" echo INI_ALL, "n"; // prints "7" - falls back to global INI_ALL echo strlen('hi'); // uses ABCstrlen echo mb_strlen('hi'); // falls back to global mb_strlen
This fallback probably exists to make it easier to use PHP’s built-in functions and constants without needing to explicitly reference them.
Importing & Aliases
PHP5 also provides a use keyword for aliasing class names and namespace names. (Functions and constants can’t be imported.) You can also use the as keyword to create an alias for the namespace during import, to shorten it and make your code more readable.
namespace foo; use MyFullNSname; use MyFullClassname as Another; NSnamesubnsfunc(); // calls function MyFullNSnamesubnsfunc $obj = new namespaceAnother; // instantiates object of class fooAnother $obj = new Another; // instantiates object of class MyFullClassname
If you are aliasing a namespace, then the name given must be fully qualified — relative paths are not allowed, and a leading slash is assumed (and in fact cannot be included).
You can also combine multiple use statements into one, separated by commas:
use MyFullNSname, MyFullClassname as Another;
Like namespaces, the use keyword is interpreted on a per-file basis and can only be used at the top-level (not inside functions or classes).
Importing is performed at compile time, so dynamic names are not affected by importing:
$obj = new Another; // instantiates object of class MyFullClassname $a = 'Another'; $obj = new $a; // instantiates object of class Another
In addition, fully qualified names are unaffected:
$obj = new Another; // instantiates object of class MyFullClassname $obj = new Another; // instantiates object of class Another $obj = new Anotherthing; // instantiates object of class MyFullClassnamething $obj = new Anotherthing; // instantiates object of class Anotherthing
Conclusion
Confused yet? It’s a bit of a mess, but I think once you start working with it it becomes easier. Of course, you probably don’t want to go overboard with multiple namespaces in a file and deeply-nested namespace hierarchies.
For more info, check out these resources:
- Name resolution rules from the PHP manual
- Namespace FAQ from the PHP manual
- Namespace series from Sitepoint
|