Table of Contents

Request for Comments: problems of namespaces and potential solutions

This RFC attempts to document all angles of the problems in namespaces in one location in order to facilitate a solution

Introduction

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 \).

Remove functions/constants from namespaces

Advantages

Problems

<?php
namespace foo;
class blah {}
function blah(){}
?>

put function in global namespace or fatal error? (code becomes more confusing)

Conflict resolution via "use namespace" or other similar proposals

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

Advantages

Problems

In my opinion, the 2nd problem renders this solution and any like it completely unusable. We need a solution that allows ::fully::delimited::names.

Introduce -> as Classname->Method() syntax

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

Advantages

Problems

be modified.

Use \ as namespace separator

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.

The big, non-obvious problems with :: as separator

Resolution of T_STRING T_PAAMAYIM_NEKUDOTAYIM T_STRING and why this kills :: as namespace separator

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.

inefficiency due to ambiguity

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 ::.

Code review gotchas

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.

Description of what it means to use \ as namespace separator

The main issues that using \ as namespace separator can address are:

Advantages

Problems

Patch

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.

Rejected Features

Changelog

* 1.1: Correct mis-representation of Lukas's understanding of Classname->method()