I agree with everything here, especially the solution to multiple namespaces per file (allow but discourage), but i don't agree with the __php__ namespace. If any separation of core php and extensions into namespaces happens, it should be kept as simple as possible, with root namespaces like "php::" and "ext::". The application should be allowed to define its own root namespace, or use a namespace such as "app::". Besides that I think all of your other suggestions are the best ones.
On Thu, 2007-12-06 at 19:36 -0600, 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 > -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: http://www.php.net/unsub.php