Hi,

This is a middle-length message with 4 parts.

Part 1:
on-list behavior

Could we all please be more efficient?  I don't care whether we get
along, but we do need to solve a problem, and endless rhetorical
flourishes != patches.

Part 2:
namespace examples.

Let's examine a realistic code sample that uses a bunch of internal and
user classes from Pyrus (PEAR installer rewritten for PHP 5.3).  A few
notes:

1) this code in svn has not yet been namespaced because the
implementation is not yet set, and so is a good test case for how to
namespace it.
2) the example is from PEAR2 code, and therefore will have PEAR2
namespace.  The autoloader can therefore be written to avoid trying to
autoload anything outside the namespace, which reduces unnecessary
directory grepping.  This is possible with any prefixed class names
(which is another good reason to prefix your names, whether with
namespaces or underscored names)
3) there are actually 2 code samples, one illustrating many userspace
classes, and the other illustrating use of internal classes, which I
will lump together as if they were 1 example.

Userspace classes:
PEAR2_Pyrus_PackageFile_v2Iterator_File
PEAR2_Pyrus_PackageFile_v2Iterator_FileAttribsFilter
PEAR2_Pyrus_PackageFile_v2Iterator_FileContents

Internal classes:
RecursiveIteratorIterator
XMLReader
DOMDocument

OK, here is the code chunk.

<?php
switch ($fake) {
            case 'contents' :
                // allows stuff like:
                // foreach ($pf->contents as $file) {
                //     echo $file->name;
                //     $file->installed_as = 'hi';
                // }
                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);
}
// next chunk
            $a = new DOMDocument();
            if ($isfile) {
                $a->load($file);
            }
        while ($this->reader->read()) {
            $depth = $this->reader->depth;
            if ($this->reader->nodeType == XMLReader::ELEMENT) {
                $tag = $this->reader->name;
// snip
                continue;
            }
            if ($this->reader->nodeType == XMLReader::END_ELEMENT) {
                return $arr;
            }
            if ($this->reader->nodeType == XMLReader::TEXT ||
                  $this->reader->nodeType == XMLReader::CDATA) {
                $arr = $this->mergeValue($arr, $this->reader->value);
            }
?>

Now, let's put this code into the PEAR2::Pyrus::PackageFile::v2Iterator
namespace, so that the long "new" statement can be much shorter.

Currently, in order for this code to be autoload-compatible, we need to
use all of the classes:

<?php
namespace PEAR2::Pyrus::PackageFile::v2Iterator;
use PEAR2::Pyrus::PackageFile::v2Iterator::File;
use PEAR2::Pyrus::PackageFile::v2Iterator::FileAttribsFilter;
use PEAR2::Pyrus::PackageFile::v2Iterator::FileContents;
?>

Without even one of the above use statements, upon the introduction (for
instance) of an internal class named "File", the code would suddenly
stop working, or worse, if method names happened to be the same,
possibly perform dangerous calling into unexpected code, which I would
even classify as a potential security vulnerability (i.e. unexpected
execution of code could be used in perfectly valid PHP code to access
the file system with code that does not do this in a different PHP
version, without code change).

Now, if we use the name resolution I have suggested, the code can work
with this single namespace line:

<?php
namespace PEAR2::Pyrus::PackageFile::v2Iterator;
?>

but has the performance slowdown of autoload being called for
PEAR2::Pyrus::PackageFile::v2Iterator::DOMDocument,
PEAR2::Pyrus::PackageFile::v2Iterator::XMLReader and
PEAR2::Pyrus::PackageFile::v2Iterator::RecursiveIteratorIterator.  The
performance slowdown can be 100% removed (with identical functionality) via:

<?php
namespace PEAR2::Pyrus::PackageFile::v2Iterator;
use ::XMLReader, ::DOMDocument, ::RecursiveIteratorIterator;
?>

Part 3:
judgment of value

Current approach:
advantages:
1) internal classes resolve very fast
disadvantages:
1) potential unexpected name resolution to internal class when
namespaced classname exists

New approach:
advantages:
1) code runs the same regardless of load order or autoload
disadvantages:
1) serious performance slowdown on each internal class name resolution

Part 4:
solving the disadvantages of the new approach

1) each internal class can be "use ::classname;" to remove this
performance hit 100% of the time
2) to detect missed classnames, add a new error level, E_DEBUG, which is
disabled by default and is used to note potentially buggy situations,
i.e. "XMLReader internal class ambiguity, use ::XMLReader if you intend
to use internal XMLReader class".  lint could enable E_DEBUG, and this
is easily implemented at parse time, since all internal classes will be
loaded.
3) a simple script that detects internal class names in a script and
adds use statements to the top of the script:

<?php
namespace NSParser;
class Parser
{
    protected $tokens;
    protected $i = -1;
    protected $classes = array();
    protected $use = array();
    protected $ret;

    function __construct($path, $namespace)
    {
        $classes = get_declared_classes();
        $classes = array_merge($classes, get_declared_interfaces());
        $this->classes = array_flip($classes);
        unset($this->classes['NSParser::Parser']);
        if (@is_file($path)) {
            $path = file_get_contents($path);
        }
        $this->tokens = token_get_all($path);
        foreach ($this->tokens as &$token) {
            if (!is_array($token)) {
                $token = array(ord($token), $token);
            }
        }
        $ret = "<?php\nnamespace " . $namespace . ";\n";
        do {
            if ($this->tokens[$this->i][0] == T_STRING) {
                if (isset($this->classes[$this->tokens[$this->i][1]])) {
                    $this->use[$this->tokens[$this->i][1]] = 1;
                }
            }
        } while (++$this->i < count($this->tokens));
        foreach ($this->use as $name => $unused) {
            $ret .= "use ::$name;\n";
        }
        $ret .= "?>" . $path;
        $this->ret = $ret;
    }

    function __toString()
    {
        return $this->ret;
    }
}
?>

The above script combined with a quick eyeball inspection would
eliminate all performance issues with minimal effort.

Does this help clarify the issue?  I think it's a no-brainer - the
performance hit is easy to solve, and well worth the risk, the current
behavior is far too close to a potential security vulnerability for my
comfort zone.

Let's also remember that an optimization that adds a 1-time addition of
1 line to source files is probably something most developers can live
with - it's a hell of a lot easier than optimizing a slow algorithm.

Thanks,
Greg

-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to