Proposed refactor of standard (and a major technical problem found with lower-casing namespaces)
| From: | Greg Beaver | Date: | Fri, 07 Aug 2009 17:51:19 +0000 |
| Subject: | Proposed refactor of standard (and a major technical problem found with lower-casing namespaces) | ||
| Groups: | php.standards | ||
| Request: | Send a blank email to standards-+get-83@lists.php.net to get a copy of this message | ||
Hi,
First off, this email is longish. It has 2 sections, the technical problem with the standard, and a concrete proposal for a better standard. The standard is after the line of asterisks.
There's a technical problem with the proposed standard, and its requirement that all namespaces be lower-cased, as illustrated by this dummy script:
<?php
namespace {
function __autoload($class)
{
var_dump($class);
}
}
namespace pear2 {
class Test {}
}
namespace pear2\Other {
use pear2\Test;
$a = new Test;
$b = new test\It;
}
?>
results in a fatal error (obviously) and this output:
string(13) "pear2\Test\It"
Fatal error: Class 'pear2\Test\It' not found in /home/user/workspace/phpsvn/pear2/BugManager/trunk/test.php on line 14
Note the case of "pear2\Test\It" <-- "Test" is capitalized because of the "use pear2\Test" statement. In other words, it becomes impossible to load a class from a sub-namespace without a full path:
<?php
namespace pear2\Other {
use pear2\Test;
use pear2\test
}
?>
results in a fatal error:
Fatal error: Cannot use pear2\test as test because the name is already in use
The problem? PHP's namespaces are not case-sensitive, but autoload translation to paths *is* case-sensitive on unix OSes. Design a standard that mixes case and *BEWM* strange fatal errors that the unaided eye may not be able to detect a reason for.
I know it seems like I have been nitpicking the standard a lot, but there is a dang good reason for it. The original standard was hastily conceived and thus needs time to find fatal flaws like the above. I hope that everyone who has been exasperated now can understand a bit better where I am coming from.
What this leaves us with is a few things that absolutely everyone agrees upon:
1) namespace separator => path separator
2) every vendor has a lower-cased vendor top-level namespace
3) all classes begin with upper-case characters <-- As PHP 6 approaches I wonder how this plays with languages like Korean that don't have upper/lower case?
4) Interfaces must be suffixed with "Interface"
There are a few things that most people agree upon, but there are a few questions to resolve
5) every package must provide a base exception <-- suggestions made include "every vendor" instead of "every package" and adding "if the package uses exceptions" as possible revisions
6) underscores allowed in package name or class name have no special meaning <-- this makes it hard to write an efficient autoloader for both Old_Style_PHP4_Namespaces and New\Namespaces, suggestion made is "underscore always translates to path separator"
7) Abstract classes suffixed with "Abstract" <-- several off-list issues raised for suffixing with "Abstract" include "what if I later decide it's not abstract? I have to break BC to rename it" and "how does one name the sub-classes? ex: pear2\Pyrus\PackageAbstract subclass pear2\Pyrus\Package\Xml or pear2\Pyrus\PackageAbstract\Xml - the first has no obvious connection, the second just looks weird. pear2\Pyrus\PackageXml results in every file in 1 directory, which forces a flat organization (directories are good for organizing things)"
*****************************************************************
For these reasons, I'd suggest splitting the standard into two segments. The new standard would start with #1-4 with status "stable", and a proposed best practices would start with #5, status "alpha" meaning it may change. The new standard could look like this:
=====
Requirements and restrictions:
(1) Each vendor shall define its own top-level namespace using lower-cased alphanumeric characters
(2) All code shall be organized into packages, each contained within their own second-level directory. For example:
pear2/
Pyrus/
SimpleChannelServer/
Packages are defined exclusively by this top-level directory.
(3) All classes should have names beginning with an upper-cased English character between A and Z. Class names should reflect the relative path within their vendor and package For example: the file pear2/Pyrus/Main.php will contain the class pear2\Pyrus\Main (namespace pear2\Pyrus, class Main)
(4) All interfaces must have the suffix "Interface." For example, an interface for "Registry" from vendor "pear2" in the package "Pyrus" will be named "pear2\Pyrus\RegistryInterface"
(5) All class-based packages should be designed for autoloading, and should not load dependencies directly using the include, include_once, require, or require_once statement, or any customized loaders. The work of loading a dependent file shall be done either by an autoloader or by the consumer of the package.
Options allowed under the standard:
(1) Packages may be distributed in any manner that the vendor would like, the definition of package shall be how it is installed inside the vendor directory. For example, the Zend Framework may choose to distribute all packages at once, whereas PEAR2 may choose to distribute packages separately, or to split up a package into components and distribute those separately.
(2) Packages may have sub-namespaces. For example, the vendor "pear2" package "Pyrus" may choose to have a class named "pear2\Pyrus\Developer\CoverageAnalyzer\Sqlite" instead of "pear2\Pyrus\DeveloperCoverageAnalyzerSqlite" or "pear2\Pyrus\Developer_Coverage_Analyzer_Sqlite"
Recommendations under the standard:
(1) It is recommended that all vendors distributing packages using a PEAR Channel should distribute each top-level package as a separate PEAR package.
=====
The draft best practices could look like:
=====
(1) Every package that throws exceptions should provide a base exception class. This may be an interface that other exceptions within the package implement.
(2) Abstract classes should have the suffix "Abstract." For example, an abstract class for "Registry" from vendor "pear2" in the package "Pyrus" would be named "pear2\Pyrus\RegistryAbstract"
(3) Underscores are not recommended for use inside a package name, but may be used. If present, underscores will be translated to path separators. For example, the class name "pear2\Pyrus_Developer\Thing" will translate to file "pear2/Pyrus/Developer/Thing.php"
(4) packages that use functions should name their functions as vendor\package\function <-- functional people, speak now, and make a recommendation if this ain't good
=====
If everyone likes one of the points from the best practices, it can be promoted to the standard.
Thanks,
Greg
P.S. function folks: importing a function into the current scope is not implemented in PHP's namespaces, even though the patch to do so is very simple. If you want to be able to do:
<?php
use function drupal\package\whatever;
whatever();
?>
then send a message to internals@ requesting this feature be added.