Hi Greg,

Thanks for the detailed thoughts.
Guides & examples regarding use-cases will definitely be important for the 
community. I am sure just this email has already been very helpful to many on 
this list.

Andi

> -----Original Message-----
> From: Gregory Beaver [mailto:[EMAIL PROTECTED]
> Sent: Thursday, December 06, 2007 5:36 PM
> To: Derick Rethans
> Cc: PHP Developers Mailing List
> Subject: [PHP-DEV] A rebuttal to Re: RFC: Dropping Namespace
> 
> 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
> 
> --
> PHP Internals - PHP Runtime Development Mailing List
> To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to