This RFC attempts to document all angles of the problems in namespaces in one location in order to facilitate a solution
The amount of pain and grief all interested developers have undergone to perfect namespaces has got to end. This RFC provides a close examination of the four potentially viable solutions and the major problems and advantages of each.
First, ways of solving the conflict between namespaced elements and static class elements:
This RFC assumes that since we are in agreement about how to resolve class names, that it is not in need of further discussion. To be clear, the agreed upon resolution order is:
which requires all internal classes to be ::prefixed (or \prefixed if the separator is changed to \).
<?php namespace foo; class blah {} function blah(){} ?>
put function in global namespace or fatal error? (code becomes more confusing)
Unfortunately, this is not an option. Here is the background:
This is actually only implementable in one way:
If we favor classes:
<?php // always calls class Blah::blah, method thing() Blah::blah::thing(); ?>
and
<?php use namespace Blah::blah; // always calls function Blah::blah::thing() blah::thing(); ?>
If we favor namespaces:
<?php // always calls function Blah::blah::thing() Blah::blah::thing(); ?>
and
<?php use class Blah::blah; // always calls class Blah::blah, method thing() blah::thing(); ?>
In my opinion, the 2nd problem renders this solution and any like it completely unusable. We need a solution that allows ::fully::delimited::names.
At first look, this appears to be an option, but as I explain below, the ambiguity between static class elements and namespaced elements is deeply embedded in the current implementation, and is not possible to resolve, thus disqualifying any solution that preserves :: as separator. However, disregarding those reasons, here are the ways to consider this option:
This could be done in a couple of ways:
The first way (which was Stas's proposal):
The second way (which was Lukas's understanding of Stas's proposal):
The second way is going to be a terrible headache for all OO authors, past and present, and will prevent migration to PHP 5.3 for those who are only looking for security fixes and performance improvements. As such, it can be safely vetoed as impractical. The next section assumes we are talking about Stas's original proposal, which is to allow this syntax:
<?php class foo { static function bar(){echo "bar\n"; } foo::bar(); // "bar" foo->bar(); // "bar"
and this syntax in case of name conflict:
<?php namespace foo::foo; function bar(){echo "bar function\n";} namespace foo; class foo { static function bar(){echo "bar\n"; } ::foo::foo::bar(); // "bar function" ::foo::foo->bar(); // "bar"
The same is true for constants:
<?php namespace foo::foo; const BAR = 2; namespace foo; class foo { const BAR = 1; } echo ::foo::foo::BAR; // 2 echo ::foo::foo->BAR; // 1
be modified.
Before discussing the advantages and problems, it is prudent to look at some of the intrinsic problems in the implementation of namespaces with :: as the separator before continuing.
With current CVS of PHP_5_3, these two code samples work very differently:
file1.php:
<?php namespace foo; function bar(){echo "func\n";}
main.php:
include 'file1.php'; class foo { static function bar(){echo "method\n";} } foo::bar(); // func ?>
<?php namespace foo::foo; function bar(){echo "func\n";} namespace foo; class foo { static function bar(){echo "method\n";} } foo::bar(); // method ?>
The actual line of code “foo::bar()” does not change, but the addition of a namespace to the file changes the name resolution. In global code, “foo::bar()” always refers to namespaced function “foo::bar.” Inside of a namespace declaration, “foo::bar()” always refers to a class method.
Although this may appear on the surface to simply be a name resolution bug, the problem is much deeper. PHP performs resolution at run-time for namespaced functions and for class methods. Basically, the opcode will check for two different scenarios:
1) in namespaced code:
2) in global code:
Note that inside the namespaced code, there is no attempt to check for [nsname]::foo::bar (foo::foo::bar). In order to implement this, we would need to have potentially 4 hash lookups for every static method call:
In this list, [called thing] is the way the method/function is called, so in the code sample above, it would be “foo::bar”, and we would check for:
This would potentially double the autoload lookups for classes, and thus is actually impossible to solve correctly with the current implementation.
The only solution is to re-define how name lookup works, such that it is different for straight “blah()” or “new blah” vs. “that::way()” or “new that::way.” However, because we use :: for the namespace separator, it is literally impossible to decide whether “that::way()” should be treated as our unqualified “way” or as qualified “that::way.” The only way to solve this problem is to use a different namespace separator.
The current implementation of namespaced functions and static methods requires code execution to go through the same location. The same is true of class constants vs. namespaced constants. For every class static method and class static constant, there is at least 1 extra hash table lookup, regardless of whether any namespaced classes/constants are defined.
As a side note, PHP 5.3 is so much faster than PHP 5.2, this is not a worthwhile comparison, and is not the best base to compare against, so I will be instead comparing PHP 5.3 CVS vs. a patched PHP 5.3 with a different namespace separator.
If another namespace separator is used, every static method call has 1 fewer hash table lookup, and every constant resolution saves potentially 2 hash table lookups (internal details can be explained if you'd like - nothing is more complicated under the hood than the way constants are resolved, so I will leave those out of this RFC). In addition, it is possible to perform much more efficient parsing of namespaced function calls than it is under the current implementation, because zend_do_begin_function_call() can be used instead of zend_do_begin_class_member_function_call(), which is much more efficient due to checks in class_member_function_call that are unnecessary for namespaced functions, but absolutely necessary if the namespace separator is ::.
Neither human review nor automated tools can look at:
<?php Blah::blah::blah(); ?>
and successfully resolve what it is in fact doing. This will hamper automated security auditing tools, code correctness tools, and human review of patches, creating a situation that makes debugging and designing robust PHP code harder than it is now. This can only be corrected via a new namespace separator.
The main issues that using \ as namespace separator can address are:
A patch against current CVS of PHP 5.3 is available at http://pear.php.net/~greg/backslash.sep.patch.txt
It is worth noting that 95% of this patch took me 2 hours to write, the constant resolution code took me an entire week, and the unit test modifications took only 1-2 hours, so porting this to HEAD should be pretty simple, but the initial work was non-trivial and I hope that the final solution could be based off of this work.
* 1.1: Correct mis-representation of Lukas's understanding of Classname->method()