Problems
I'm not talking about the separator symbol. It's an irrelevant detail. I suspect that bikeshedding about it diverted attention from real issues.
I've found namespaces introduced in PHP 5.3 to suffer from multiple usability problems due to performance trade-offs, limitations of PHP's parser, collisions with other PHP features and unfortunate design decisions.
You can't import a namespace
PHP's use
statement can only import names of
individual namespaces/classes. There is no way to mass-import a
namespace:
use MyProject\Feature\*; // Not in PHP
No matter what you do, you can't avoid writing qualified class names at least once. Unless the names are used multiple times, namespaces end up being net negative for code length.
Fixed in PHP 7: There used to be no shorthand for importing multiple names from a namespace, but now the use
statement accepts groups:
use FooLibrary\{Foo, Bar, Baz};
You can't avoid repeating use
statements across files
That is not to say that use
should work globally, as this obviously defeats the purpose.
Because imports are verbose and sometimes tedious to maintain, it would be nice to avoid manually repeating them in every file and e.g. load most often used set of imports from a file.
use <common_set_of_imports_that_my_project_uses.php> // No! Use copy&paste instead!
Workaround via class_alias()
leaves a lot to be desired:
- It loads classes immediately, so it's not suitable for emulation of
use Ns\*
. - It creates a name confined to a specific namespace, leaving choice between a global name (defeating the purpose of namespaces) or a name in some namespace (which still requires
use
statements in projects using sub-namespaces).
Forgotten imports/aliases
It's impractical to simply copy use
statements for all classes to all files, so
you need to pick which imports are going to be declared in each
file, and the list is going to vary from file to file.
As a result, whenever an unqualified name is used in a file, you need to ensure that it is present in the file's list of imports. Errors caused by a missing alias won't be reported until the code is run.
Moving of namespaced code between files is harder and more error-prone. Aliases have to be copied, but they need to be copied selectively, as it is an error to declare the same alias twice.
Aliases don't work with APIs operating on classes
Fixed in PHP 5.6: When namespaces were introduced there was no straightforward way to get a fully-qualified name from an aliased name used in code. Later PHP has added ClassName::class
syntax for this.
namespace MyProject;
use Library\Stuff\FoosAndBars\Foo;
new \ReflectionClass('Foo'); // Error!
new \ReflectionClass(Foo::class); // Works now
PHP namespaces increase the chance of collisions with reserved words
Namespaced names are not tokens in the PHP parser
(\
is a separator token), which means that
use of a reserved word as any part of the namespace's “path” is a
parse error. It's possible for code that works without
namespaces:
new List_DoublyLinked();
become invalid when namespaced:
new List\DoublyLinked(); // Name collision!
The above is a parse error, because list
is a
reserved global keyword. This problem cannot be avoided with
use
statement, as it won't parse either.
use List as DontCollide; // will collide anyway!
Nested namespaces are half-baked
PHP's concept of sub-namespaces is only a shallow syntactic sugar for explicit references to other namespaces. Unlike in some other languages, it does not affect lookup of unqualified names.
Sub-namespaces are not “embedded” in their parent namespace. They are separate top-level namespaces that only happen to share a name prefix:
namespace GUI;
class View {}
namespace GUI\Widgets;
class PushButton extends View {} // PHP won't find the View
The code above doesn't work, because PHP treats
GUI
and GUI\Widgets
as two independent
namespaces. Sub-namespaces can't simply use classes of their
“parent” namespaces. Namespace declarations can't even be nested.
Name lookup is quirky and inconsistent
It's quite easy to notice that lack of automatic lookup of
names in parent namespaces leads to explosion of use
statements or use of verbose fully qualified names. PHP fixes this
problem, but only for function and constant names and only for the global
namespace:
namespace Foo\Bar;
\time(); // works, but is ugly
time(); // PHP is smart enough to fall back to global
new \Date(); // ugly and required
new Date(); // PHP is not smart any more
The above will fail if Foo\Bar\Date
doesn't exist
and PHP will not try to find Foo\Date
nor
even Date
.
The Foo
namespace is skipped when searching for
functions, so a global time()
function would be used
even if Foo\time()
existed.
Fully-qualified name doesn't work in class declaration
You can't do:
class Foo\Bar\Someclass {…}
You have to do:
namespace Foo\Bar;
class Someclass {…}
Because of this limitation you can't pick the most convenient namespace when defining classes, e.g. use project's top-level namespace for all files (for consistent, searchable names in all files) or use most-often used namespace to reduce amount of names that need to be aliased/fully-qualified.
This is especially annoying when migrating existing
“underscore-namespaced” code to native namespaces as class
Foo_Bar
can't be simply changed to class
Foo\Bar
.
Popular PHP projects overuse sub-namespaces
Unfortunately it has become a convention in PHP to map
directory structure directly to namespaces. Sometimes even
CamelCasing is replaced with namespaces (e.g.
FooController
becomes Controller\Foo
)
and even exceptions get their own namespace (e.g. Framework\Component\Form\Exception\UnexpectedTypeException
).
Use of hundreds of deeply nested sub-namespaces with one or two classes each make limitations of name lookups and aliases more obvious.
Namespaces cannot be autoloaded
“Poor man's namespacing” done with static methods and class constants has a nice side-effect of using PHP's autoload.
Classname::CONSTANT; // Works reliably if autoload is used
Namespacename\CONSTANT; // Won't be autoloaded and might work only by accident
Migration to namespaced constants and functions creates a risk: namespaced constants and functions will usually work in unit tests (which happen to load lots of files and that state is not isolated between tests), but may fail in production when only one narrow code path is executed and the relevant file isn't autoloaded in time.
new Lib\Obj(Lib\FOO_MODE); // works
$mode = Lib\FOO_MODE; // suddenly fails!
new Lib\Obj($mode);
Conclusion
Namespaces in PHP 5.3 are not a clear win over unnamespaced or “prefix-namespaced” code. Terseness of unprefixed names and robustness against collisions can be overweighed by verbosity of alias declarations and fragility of repetitive boilerplate. PHP 7 has fixed some issues, but major problems remain unsolved.
For large and diverse frameworks any namespaces might be better than nothing, but for more controlled projects PHP namespaces may not be beneficial in their current form.
Possible solutions
I don't think that having to use a heavy-weight IDE that parses whole project and generates boilerplate code automatically is the right solution. I hope improvements can be made at the language level to keep it usable without high-tech life support.
Namespaces in a dynamic and messy language like PHP are a hard problem, so I don't claim to have perfect solutions. There are things that might help though:
- Change qualified names to be a single token, or make the grammar accept other tokens as names. This would create a way to avoid collisions with PHP reserved keywords.
- Extend language grammar to allow qualified names in class declarations. References to class names should behave uniformly. Class declarations and namespaces should be orthogonal.
- Extend
autoload
to load functions and constants when they are referenced via qualified names. Unfortunately, unqualified names need to remain quirky (requirement to always use\strlen()
rather thanstrlen()
would be unacceptable).
With the current state of things, I prefer to avoid namespaces in my code — dealing with a collision once in a while is less work than dealing with namespaces all the time.