PHP6 Namespaces patch backported

at 2007-07-21 in Further reading by friebe (0 comments)

Beginning of this month, Dmitry Stogov from Zend published a patch implementing namespaces in PHP6.

To be able to begin playing around with it without adding further points of possible failures for our XP unittests with new PHP6 (e.g., unicode-related breakage), I started and after a day's work succeeded in backporting the patch to current CVS HEAD, that is, PHP 5.2.4-dev.

Here's a list of problems encountered backporting the patch:

Unicode API
PHP6 has a two types of strings, unicode and binary (the latter is what PHP5 has today). Therefore, any place handling strings in PHP's C sourcecode must test for both of them and handle them differently. The patch contains numerous places which look like this:

  if (Z_TYPE_P(callable) == IS_UNICODE &&
Z_USTRVAL_P(callable)[0] == ':' &&
Z_USTRVAL_P(callable)[1] == ':') {
lmname = zend_u_str_case_fold(IS_UNICODE, (zstr)(Z_USTRVAL_P(callable)+2), Z_USTRLEN_P(callable)-2, 1, &mlen);
} else if (Z_TYPE_P(callable) == IS_STRING &&
Z_STRVAL_P(callable)[0] == ':' &&
Z_STRVAL_P(callable)[1] == ':') {
lmname = zend_u_str_case_fold(IS_STRING, (zstr)(Z_STRVAL_P(callable)+2), Z_STRLEN_P(callable)-2, 1, &mlen);
}
...thus significantly bloating the patch (and the effort to develop it, in the first place! - but that's another story:))
This makes it impossible to apply the patch directly and turns backporting it into a tedious task, having to reduce the above to the second branch (IS_STRING) and removing ifs where only IS_UNICODE tests are done.

Additionally, other unicode artifacts need refactoring - for example, zend_u_str_case_fold (not existant in PHP5 API) to zend_str_tolower_dup; zstr*s must become char*s, and the unions involving these must be removed. Here's an example:
  if (Z_TYPE(class_name->u.constant) == IS_UNICODE) {
compound.u = u_memchr(Z_USTRVAL(class_name->u.constant), ':', Z_USTRLEN(class_name->u.constant));
} else {
compound.s = memchr(Z_STRVAL(class_name->u.constant), ':', Z_STRLEN(class_name->u.constant));
}
if (compound.v) { /* ... */ }
and what became of it in PHP5:
  compound = memchr(Z_STRVAL(class_name->u.constant), ':', Z_STRLEN(class_name->u.constant));
if (compound) { /* ... */ }

Line number changes
Because of the unicode changes in PHP6 and extensive source lines shifts resulting of them, it proved to be quite difficult to find the correct places to patch in some situations.

Missing documentation
As always and any place in PHP, inline documentation is rare to find - the almost 2800 lines patch contains exactly one new inline comment. It took me quite a while to understand the inner workings of the patch.

WTF
Several cases in the original patch cannot possibly work, or I am missing something. So for example the code inside zend_is_callable_check_func (which is responsbile of checking if a given value is callable) do not consider the ::foo() case (which should call the global function foo()) or the ::ClassName (which should look up a class ClassName inside the main namespace). For both situations, .phpt files were supplied. Note: I did not check the PHP6 implementation because I have so far not succeeded in compiling PHP6 due to missing requirements, so I'm not sure... but the relevant pieces of source still look suspicious.

General C rant
  len= 2 + Z_STRLEN(name->u.constant);
Z_STRVAL(result->u.constant)= (char*) safe_emalloc(len+ 1, 1, 1);
Z_STRVAL(result->u.constant)[0]= ':';
Z_STRVAL(result->u.constant)[1]= ':';
memcpy(Z_STRVAL(result->u.constant)+2, Z_STRVAL(name->u.constant), Z_STRLEN(name->u.constant)+1);
Z_STRVAL(result->u.constant)[len]= '\0';
Don't you just hate this? Wait, here's the PHP version:
<?php 
$result= '::'.$name;
?>



Inner workings
Here's some key figures:

Compile Time
  • The compiler prefixes declarations by prepending the current namespace name.
  • Prefixing happens in class and interface declarations, function declarations, and catch blocks and uses zend_do_build_namespace_name()
  • A list of current imports is kept in a hashtable called CG(current_import)
  • When a class constant is encountered (e.g. new Exception(), the name is resolved to its fully qualified name using zend_resolve_class_name()
Runtime
  • Statements such as new $class or $func(); cannot be resolved at compile time and are deferred until runtime
  • For function and static method calls, everything is done at runtime. This is to resolve ambigouos statements such as foo::bar::baz(); which may either be a function call to a function baz() inside the namespace foo::bar or a static member call to a class bar (inside namespace foo)'s static method baz.


Success!
After a day's work I finally succeeded in running all .phpt tests Dmitry had written. Here's the patch:

Download PHP5 namespaces patch

It should apply cleanly against PHP's PHP_5_2 branch. Recompile (ensure you've excuted make clean before, some of the Zend structs have changed!) and you're set up with a PHP5 with PHP6 namespace support!



Subscribe

You can subscribe to the XP framework's news by using RSS syndication.


Categories

News
General
PHP5
Announcements
RFCs
Further reading
Examples
Editorial
EASC
Experiments
Unittests
Databases

Related

Find related articles by a search for «PHP6».