Verification: a143cc29221c9be0

Php classes with same name

Содержание

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\*; 

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 

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'); 
new \ReflectionClass(Foo::class); 

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(); 

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; 

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 {} 

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(); 
time(); 

new \Date(); 
new Date(); 

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; 
Namespacename\CONSTANT; 

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); 
$mode = Lib\FOO_MODE; 
new Lib\Obj($mode);