Hi Greg,

I'm speechless. All of your points are sound and you've obviously done your homework, here. I agree with Ken in that you've gone above and beyond on the research and arguments you've presented here.

Excellent work and a hearty +1 to all of your points.

Jeremy

Gregory Beaver wrote:
Hi Derick,

I've been thinking a *lot* about your provocative email in the past
couple of days, and have come to a different conclusion from my original
reply (which, as a reminder stated I saw no problem with removing
namespaces as long as we kept the import facility to alias classes via
use).  After a few hours of thinking practically about the
implementation, I realized that this would be impossible for the same
reasons as your first argument.  So, I set out to find a solution that
keeps namespaces and solves the problems.

I have found the solutions to every problem I could find, and to my
surprise, even after crafting a new patch to solve some of them, have
found a way to solve all of the problems within the existing
implementation (with a few tweaks, but nothing major changed) or by
specifying new coding conventions.

Summary:

1) recommend all global non-namespaced code that wishes to import
namespaced code via "use Namespace::Classname" add a "namespace
__php__;" at the top of the file, and that the __php__ namespace be
reserved for use by end-user applications.
2) use Name::*; is technically impossible without some kind of
autoloading mechanism, but can be reasonably approximated by importing a
long namespace name as a shorter one.  "use PEAR2::Really::Long::Name as
a;" then allows referring to PEAR2::Really::Long::Name::Classname as
a::Classname, and PEAR2::Really::Long::Subnamespace::Classname as
a::Subnamespace::Classname.
3) multiple namespaces per file should be allowed, but strongly
discouraged as a coding practice of last resort for most users.
4) brackets for namespaces is most necessary for hierarchical namespace
implementations.  This is not such an implementation, and as such
brackets do not add clarity but do slow down the implementation.
5) namespaces provide these benefits that are not available in PHP:
   a) short, unqualified descriptive class names can be used without
fear of conflicting with an internal class
   b) a clear, uniform coding convention on how to avoid conflicts
between your code and others that will help those who have to maintain
the code of others.
   c) a way to alias longer names to increase readability and
maintainability of code.
6) There are some dangers when using namespaces with autoload, but
simple ways of thwarting those dangers.  Also dangerous is trusting
external code that is in the same namespace, as functions can be redefined.

Detailed answers:

Derick Rethans wrote:
1. As it is impossible to do "use XXX as NativeClass" we get to the
<snip>
   extension (DateTime, DateTimeZone). However introducing the new class
   DateTimeSpan might break people's code that do things like:

        <?php
        use myNamespace::DateTimeZone as DateTimeZone;
        ?>

This is indeed the biggest problem.  However, it only exists in the
global "namespace" (non-namespaced code).  An example script using the
non-existing hypothetical PEAR2::DateTime class:

<?php
include 'PEAR2/Autoload.php';
use PEAR2::DateTime; // fatal error - use name conflicts with internal class
$a = new DateTime;
?>

However, the answer is simple and elegant.  PHP applications that take
advantage of namespaces should use a namespace *in the application
global code* that is reserved for application code, like __php__.

<?php
namespace __php__;
include 'PEAR2/Autoload.php';
use PEAR2::DateTime;
$a = new DateTime; // $a is an object of class PEAR2::DateTime after
autoloading, or if a previously included file has declared class
DateTime in namespace PEAR
?>

Note that the only difference here is the addition of 1 line of code at
the top of the file.  On the same token, this code:

<?php
namespace __php__;
$a = new DateTime; // $a is an object of class ::DateTime
?>

works as expected, accessing the internal DateTime class directly.

In other words, 1 line of code is needed to take advantage of
namespace's full protection and ability to import conflicting class
names into the "global" (in this case unqualified, not containing :: in
the name) scope, while at the same time preserving BC with existing code
(no modification needed beyond addition of "namespace __php__;").

I recommend that the manual specify this convention, and will happily
take on the documentation of it.

2. You have to import every class yourself. You can currently not do:
use myNamespace::* as *; // or similar syntax

Actually, ::* would only be deterministic if use performed some kind of
autoloading, it's not a question of runtime versus compile-time - it's
simply not possible to determine which class is intended if more than
one ::* is used unless the answer is determined by code loading, this is
why Java/Python import actually loads code as well as aliasing identifiers.

There are ways of simulating "use myNamespace::* as *;" that are almost
as complete and are actually better for long-term maintenance.  For
instance, let's say you want to use several classes from
PEAR2::Ultra::Long::Package::Name.  You don't need to use each class
individually, you can import the entire namespace:

<?php
namespace __php__;
use PEAR2::Ultra::Long::Package::Name;
$a = new Name::ExampleClass;
$b = new Name::SecondThing;
$c = new Name::ThirdThing;
?>

or the ultra-brief method, requiring 3 extra characters over an
unqualified name:

<?php
namespace __php__;
use PEAR2::Ultra::Long::Package::Name as a;
$a = new a::ExampleClass;
$b = new a::SecondThing;
$c = new a::ThirdThing;
?>

Even more significant, you can use sub-namespaces with this system, each
namespace does not need to be imported separately:

<?php
namespace __php__;
use PEAR2::Ultra::Long::Package::Name as a;
$a = new a::ExampleClass;
$b = new a::SecondThing;
$c = new a::ThirdThing;
$d = new a::subnamespace::MyClass; // $d is an object of class
PEAR2::Ultra::Long::Package::Name::subnamespace::MyClass
?>

This is dramatically easier to debug than the equivalent import of ::*
(with an added namespace for dramatic effect):

<?php
namespace __php__;
use PEAR2::Ultra::Long::Package::Name::*;
use Symfony::Something::*;
$a = new ExampleClass;
$b = new SecondThing;
$c = new ThirdThing;
$d = new subnamespace::MyClass;
$e = new Controller; // which package is this class from?
?>

Looking at the code, it is impossible to determine which package the
classes come from.  Here is the same example with current syntax:

<?php
namespace __php__;
use PEAR2::Ultra::Long::Package::Name as a;
use Symfony::Something as s;
$a = new a::ExampleClass;
$b = new a::SecondThing;
$c = new a::ThirdThing;
$d = new a::subnamespace::MyClass;
$e = new s::Controller; // now we know for certain this is from Symfony
?>

3. We keep bickering over using { } or not, multiple namespaces in a
   file or not... etc. I understand that people want more flexibility,
   but also we need a *simple* to explain implementation. With the
   current implementation I see the following problems here:

   - You can't stick multiple namespaces in one file
   - Unlike other constructs in PHP that mark executable blocks,
     namespaces don't use { }.
   - The "namespace" keyword at the start of a file is somewhat magic,
     because it can only appear as first element in your script.

I have wrestled with this many times, and the best conclusion I can
reach is that (1) we need the flexibility of multiple namespaces per
file, but that (2) we need to *strongly* discourage it for the reason
that it is going to make debugging harder.  There are two valid use
cases for multiple namespaces per file that I have encountered:

1) performance.  PHP is slightly slower when executing stuff in multiple
files
2) GTK2.  It's always nice to be able to put a GTK2 app in a single file
and this is an alternative to using a phar archive.

There are two philosophies behind namespaces out there, hierarchical and
non-hierarchical.  In hierarchical languages like C++ and C#, nested
namespaces can be defined, and sub-namespaces can be defined using
nested code blocks.  Other implementations simply don't do this.  The
best description I've seen is from a site on Perl namespaces:

"Isaac Newton is not related to Olivia Newton-John, and Newton::Isaac is
not related to Newton::John::Olivia."

Perl, incidentally, uses the package keyword as a combination of
declaration and alias, in that there is no equivalent to PHP's use.

PHP's implementation is a mixture of these approaches.  Because it is
closer to Perl's implementation of package, but contains the import
statement from compiled languages like C++, we have to forge a new path.

Having namespace as the start of the file is good because it enforces a
declaration "take note: this file uses namespaces" that might otherwise
be lost.

I have no strong opinion on {}, but I do see a compelling argument in
favor of not using {} for performance reasons if you're only going to
use 1 namespace per file except in extraordinary circumstances.

4. What is wrong with simple prefixes in the first place? Both PEAR_*,
   Zend_*, ezc*, and ezp* are perfectly acceptable markers for different
   'namespaces'.  We could optionally create a registry on php.net for
   this to avoid conflicts.

The only naming conflicts we've had have been with internal classes thus
far, but there is no coding convention in place.  Namespaces would
provide a technical solution to a political problem, something that
carries with it intrinsic risks.  Fortunately, other languages have
taken this risk before PHP and not had major problems, so we have a
reasonable expectation of success.  The implementations of namespacing
vary so widely, it's kind of impossible to generalize on how they do it,
but difference between prefixing and namespaces can be exemplified by
one construct: use.

<?php
// assume we've loaded code for brevity
$a = Zend_Long_Class_Name::singleton(Zend_Long_Class_Name::CONST);
$b = new PEAR2_Long_Class_Name();
Zend_Long_Class_Name::$var = 2;
?>

<?php
namespace __php__;
use Zend::Long::Class as a;
use PEAR2::Long::Class as b;
$a = a::Name::singleton(a::Name::CONST);
$b = new b::Name();
a::Name::$var = 2;
?>

Also possible with namespaces is switching out an implementation with a
similar API without search/replace (assuming no dynamic class references
are used)

<?php
// before
namespace __php__;
use Zend::Thing::Implementation;
$a = new Implementation;
?>

<?php
// after
namespace __php__;
use PEAR2::Does::Same::Thing as Implementation;
$a = new Implementation;
?>

With all the above considerations, especially my first point, I still have not
heard any good reason why namespaces in the current implementation are actually
useful - or what particular case they solve... so I am wondering, are they
really useful? I come now to the conclusion that they are not, and for myself
(and most likely my work projects) I would have to decide not to go with
namespaces, but instead stick with the 3 letter prefixing.  Something that I
have totally no problem with, as it is nice and easy.

1) The ability to guarantee short names can be used without danger of
conflicting with a global name.  This is possible inside any namespace
declaration, but not in global scope.

2) readability, and therefore maintainability increases as redundant
parts of identifiers are removed by use aliasing.  This line:

<?php
                return new PEAR2_Pyrus_PackageFile_v2Iterator_File(
                        new
PEAR2_Pyrus_PackageFile_v2Iterator_FileAttribsFilter(
                        new PEAR2_Pyrus_PackageFile_v2Iterator_FileContents(
                            $this->packageInfo['contents'], 'contents',
$this)),
                            RecursiveIteratorIterator::LEAVES_ONLY);
?>

becomes:

<?php
use PEAR2::Pyrus::PackageFile::v2Iterator as i2;
use ::RecursiveIteratorIterator as ri; // currently not possible in CVS
but I have a patch...
// ...

                return new i2::File(
                        new i2::FileAttribsFilter(
                        new i2::FileContents(
                            $this->packageInfo['contents'], 'contents',
$this)),
                            ri::LEAVES_ONLY);
?>

The argument has been made that namespaces can actually increase code
size because class names are so rarely used.  This is not true in my
coding experience.  As an example, a single file in PEAR2_Pyrus has 25
uses of the classname PEAR2_Pyrus_PackageFile_Exception.  Any class
making use of class constants used by another class will be using the
full classname a lot.

3) Ability to use common short names (like File, Exception, Date) as
class names within a namespace increases the logical descriptive
qualities of the code, making it easier to understand without
documentation (self-documenting code).  For example, in the classname
PEAR2::Pyrus::Config, the only part that really describes anything
important for the user to know about is "Config", the rest is redundant.

In terms of the danger of using namespaced classes with autoload, the
biggest danger is this code:

<?php
namespace Blah;
$a = new Exception('hi');
?>

In this case, the ::Exception class is instantiated.

<?php
namespace Blah;
use Blah::Exception;
$a = new Exception('hi');
?>

Now we guarantee the correct class is loaded.  Although this seemed
onerous to me at first, on reflection, there are very few internal
classes that are likely to be overridden by a package (only 98 exist in
my 5.3 build), probably at most 2 in any given package, so this is not a
huge burden.

A larger danger is this example:

<?php
namespace foo;
echo strlen('hi');
include 'maliciousfile.php';
echo strlen('hi');
?>

maliciousfile.php:
<?php
namespace foo;
function strlen($a)
{
    // do bad stuff
    echo "I'm evil"
    return ::strlen($a);
}
?>

The above script outputs "2I'm evil2" - foo::strlen() is called for the
second call to strlen().  Application authors like blogs that run
plugins must be extremely cautious that plugins from third parties
cannot contain redefinitions of core functions inside the blog's
namespace.  Of course, any third-party code is dangerous regardless of
language construct, but this is a subtle vector that could be used and
would not be caught by a code scanner unless consciously coded in to
watch for a "namespace foo;" declaration.

I hope this is helpful - by digging deeply into the source and talking
to lots of devs, I have learned a lot about PHP's namespace
implementation in the past 2 days, and I'm certain we have a much better
implementation than I thought we did 2 days ago, and will be writing up
a "how to namespace" and "how not to namespace" guide based on the
pitfalls I've discovered in this quest as well as contributing to the
documentation when things settle.

Greg


Reply via email to