Hi,
I'm looking at the current namespace resolution rules here:
http://www.php.net/manual/en/language.namespaces.rules.php
Something that bothers me is that for the first time in PHP we introduce a
hard difference in how internal functions and user functions are
resolved/called:
::foo(); // resolves as user function in global namespace
foo(); // can resolve as function in the current namespace, else as internal
function
Whether a function/class is internal is implementation detail that I believe
shouldn't be forced in such a fashion in user code.
It makes people to check/think whether a function is internal or not,
additionally to thinking about namespace scope. It's unneded complexity. If
you check the PHP manual comments, you'll notice people often introduce PHP
userspace equivalents to various functions and extensions, there's for
example a full PHP-only implementation of curl:
http://code.blitzaffe.com/pages/phpclasses/files/libcurl_emulator_52-7
There was also a wave of drop-in replacements for PHP5 function in PHP4, or
for example json_en/decode() drop-in for PHP 5.1 users who don't have the
PECL extension.
None of those will be possible in namespace scope in 5.3, and a developer
will need to have plenty of special cases in code or special factories, if
it's not known in advance whether a class/function will be internal or not.
To further convolute things, the drop-ins will work if not in namespace
context.
A second problem is one of future-proofing. Many of the procedural API-s in
PHP4 saw transition to OOP in PHP5. If namespaces have success and are
adopted widely, we may see new or existing internal functions make similar
transitions to being namespaced. This will leave "global internal" function
stick our like a sore thumb, as a solitary special case with specific
syntax, as opposed to all other cases we have.
A third problem is one of performance. Every time a developer calls an
internal function, an attempt will be made to resolve it to a user function
in the current namespace, and only then call the internal function. Typical
PHP code contains plenty of internal function calls, while I believe the
novelty of being able to transparently override intenral functions will wear
off quickly and be used quite sparingly (and quite likely be considered a
bad practice, leading to confusing code).
What I suggest:
::foo(); // to resolve a user or internal function in global namespace (in
namespace context)
foo(); // to only resolve at compile time, in the current namespace (in
namespace context)
Much simpler to understand, more future-proofed, faster (more cases can be
covered by compile-time resolution), and allows PHP drop-ins for
binary/internal functionality.
To recap, problems with the current approach that are resolved by what I
suggest:
- Exposing internal/user functions in userspace code as two different
things. - Drop-in replacements for internal functionality won't work in namespace
context. - Problems when internal functions/classes start showing up in namespaces
in the future. - Performance hit, as more things need to be resolved at runtime that it's
needed.
Please give me your feedback and use cases.
Regards,
Stan Vassilev
Hi Stan,
I made a proposal and patch a few months ago at
http://news.php.net/php.internals/34097, which Julian later adapted to a
more recent version of PHP 5.3 at the time at
http://news.php.net/php.internals/36822 (don't know if the patch still
applies cleanly).
Unfortunately I haven't received many comments on the patch nor any
reasons on why it shouldn't be merged. Let me know what you think of the
resolution rules I proposed.
--
Jessie Hernandez
Zend Certified Engineer (http://zend.com/zce.php?c=ZEND006359&r=222727282)
Stan Vassilev | FM wrote:
Hi,
I'm looking at the current namespace resolution rules here:
http://www.php.net/manual/en/language.namespaces.rules.phpSomething that bothers me is that for the first time in PHP we introduce a
hard difference in how internal functions and user functions are
resolved/called:::foo(); // resolves as user function in global namespace
foo(); // can resolve as function in the current namespace, else as internal
functionWhether a function/class is internal is implementation detail that I believe
shouldn't be forced in such a fashion in user code.It makes people to check/think whether a function is internal or not,
additionally to thinking about namespace scope. It's unneded complexity. If
you check the PHP manual comments, you'll notice people often introduce PHP
userspace equivalents to various functions and extensions, there's for
example a full PHP-only implementation of curl:
http://code.blitzaffe.com/pages/phpclasses/files/libcurl_emulator_52-7There was also a wave of drop-in replacements for PHP5 function in PHP4, or
for example json_en/decode() drop-in for PHP 5.1 users who don't have the
PECL extension.None of those will be possible in namespace scope in 5.3, and a developer
will need to have plenty of special cases in code or special factories, if
it's not known in advance whether a class/function will be internal or not.To further convolute things, the drop-ins will work if not in namespace
context.A second problem is one of future-proofing. Many of the procedural API-s in
PHP4 saw transition to OOP in PHP5. If namespaces have success and are
adopted widely, we may see new or existing internal functions make similar
transitions to being namespaced. This will leave "global internal" function
stick our like a sore thumb, as a solitary special case with specific
syntax, as opposed to all other cases we have.A third problem is one of performance. Every time a developer calls an
internal function, an attempt will be made to resolve it to a user function
in the current namespace, and only then call the internal function. Typical
PHP code contains plenty of internal function calls, while I believe the
novelty of being able to transparently override intenral functions will wear
off quickly and be used quite sparingly (and quite likely be considered a
bad practice, leading to confusing code).What I suggest:
::foo(); // to resolve a user or internal function in global namespace (in
namespace context)
foo(); // to only resolve at compile time, in the current namespace (in
namespace context)Much simpler to understand, more future-proofed, faster (more cases can be
covered by compile-time resolution), and allows PHP drop-ins for
binary/internal functionality.To recap, problems with the current approach that are resolved by what I
suggest:
- Exposing internal/user functions in userspace code as two different
things.- Drop-in replacements for internal functionality won't work in namespace
context.- Problems when internal functions/classes start showing up in namespaces
in the future.- Performance hit, as more things need to be resolved at runtime that it's
needed.Please give me your feedback and use cases.
Regards,
Stan Vassilev
Jessie Hernandez wrote:
Hi Stan,
I made a proposal and patch a few months ago...
The developers should really take a serious look at this issue or it
will come back to haunt them later. I'm not sure why no one seems
comment on your proposal and patch. It seemed like a well thought-out
and good proposal to me.
I'm also wondering what happened to the namespace declaration change to
brackets {}. I've downloaded the latest snap of 5.3 and that has not
changed as of yet.
Ryan Panning wrote:
Jessie Hernandez wrote:
Hi Stan,
I made a proposal and patch a few months ago...
The developers should really take a serious look at this issue or it
will come back to haunt them later. I'm not sure why no one seems
comment on your proposal and patch. It seemed like a well thought-out
and good proposal to me.I'm also wondering what happened to the namespace declaration change to
brackets {}. I've downloaded the latest snap of 5.3 and that has not
changed as of yet.
is it too late to scrap all this and go with Java/AS3 style
base.package.class please?
Nathan Rixham wrote:
Ryan Panning wrote:
Jessie Hernandez wrote:
Hi Stan,
I made a proposal and patch a few months ago...
The developers should really take a serious look at this issue or it
will come back to haunt them later. I'm not sure why no one seems
comment on your proposal and patch. It seemed like a well thought-out
and good proposal to me.I'm also wondering what happened to the namespace declaration change
to brackets {}. I've downloaded the latest snap of 5.3 and that has
not changed as of yet.is it too late to scrap all this and go with Java/AS3 style
base.package.class please?
yes
Greg
Nathan Rixham wrote:
Ryan Panning wrote:
Jessie Hernandez wrote:
Hi Stan,
I made a proposal and patch a few months ago...
The developers should really take a serious look at this issue or it
will come back to haunt them later. I'm not sure why no one seems
comment on your proposal and patch. It seemed like a well thought-out
and good proposal to me.I'm also wondering what happened to the namespace declaration change
to brackets {}. I've downloaded the latest snap of 5.3 and that has
not changed as of yet.is it too late to scrap all this and go with Java/AS3 style
base.package.class please?yes
Not saying that it is a good thing to do that but it is not too late
to change things.
Cheers,
Hi,
I'd like to nudge the discussion back to issues with the resolution rules
that we're discovering :)
The actual char(s) used can only be mildly annoying to some (at worst),
compared.
Can we please agree on those (or explain why you'd not):
- functions, constants and classes should use the same resolution rules.
Making special cases just for some of them, or just for user or just
internal ones will lead to confusion. - can someone please explain why is it useful to override internal function
inside a namespace. Isn't it better to explicitly refer to global functions
as global, which would allow compile-time resolution in a lot more cases
than now.
I said before, it looks like the current situation where you can define
globally a PHP-only clone of a missing binary/internal features is a far
more predictable behaviour, and people use it a lot right now. Do we
really want to break this in 5.3?
Regards,
Stan Vassilev
is it too late to scrap all this and go with Java/AS3 style
base.package.class please?yes
Not saying that it is a good thing to do that but it is not too late
to change things.Cheers,
Pierre
Stan Vassilev | FM wrote:
Hi,
I'd like to nudge the discussion back to issues with the resolution
rules that we're discovering :)
The actual char(s) used can only be mildly annoying to some (at worst),
compared.Can we please agree on those (or explain why you'd not):
- functions, constants and classes should use the same resolution
rules. Making special cases just for some of them, or just for user or
just internal ones will lead to confusion.
I have to agree. In fact, there is a simple and elegant solution to the
problem that involves augmenting Jessie's patch to include a more
helpful error message on class/function not found, and a simple 51-line
PHP script for converting non-namespaced to namespaced code. With these
two things, I think we could safely change the name resolution in PHP.
- can someone please explain why is it useful to override internal
function inside a namespace. Isn't it better to explicitly refer to
global functions as global, which would allow compile-time resolution in
a lot more cases than now.
The reasoning behind this decision that I was given by Dmitry and
Stanislav was 2-fold.
- Currently, the lookup order is optimized for internal
functions/classes [more detail below] - migrating existing code to namespaces will require much more work if
all internal functions/classes must be prefixed with :: or "used" to be
avialable
Detail:
- Currently the lookup order is optimized for internal functions/classes.
At compile-time, all internal classes/functions exist. However, it is
possible that not all userspace functions/classes exist yet. As such,
if a T_STRING
is encountered, here is how the resolution works currently
in the worst case (roughly, in pseudo-code):
=> is it an internal function/class? if so, use it
=> does it exist in the current namespace? if so use it
=> otherwise, mark it for runtime resolution
=> run code
=> when we reach this opcode, resolve the function
In the best case:
=> is it an internal function/class? use it
However, changing this to allow resolving always to the current
namespace would change this to:
worst case:
=> does it exist in the current namespace? if so use it
=> otherwise, mark it for runtime resolution
=> run code
=> when we reach this opcode, resolve the function
best case:
=> does it exist in the current namespace? if so use it
This results in a fatal error for all internal functions/classes. The
error message for a non-existing class/function should at least hint
that a use statement is needed, an element missing from Jessie's patch.
In any case, #1 proves to be false, as adding a "use" clause for each
internal function/class is no performance hit at all.
- migrating existing code to namespaces will require more work if all
internal functions/classes must be prefixed with :: or "used"
As a test, I tried adding a namespace declaration to the top of one of
PEAR's larger file, and then ran it with PHP 5.3 and the patch. To my
(initial) surprise, it ran without error. On further investigation, I
realized why this is so - none of the internal functions/classes were
actually resolved, all of them were delayed until runtime, and none were
actually executed.
What this means is that by adding a namespace declaration, it is quite
possible that your code will execute happily until it suddenly
encounters a function/class you forgot to "use" or prefix with ::.
Fortunately, I just wrote a simple namespace conversion script that
solves this problem decisively. The eventual script should be checked
over for missing userspace class/functions, as only internal
class/functions will be "use"d. Here it is:
<?php
namespace NSParser;
class Parser
{
protected $tokens;
protected $i = -1;
protected $classes = array();
protected $functions = 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']);
$functions = ::get_defined_functions();
$this->functions = ::array_flip($functions['internal']);
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;
}
if (isset($this->functions[$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;
}
}
?>
Greg
Hi!
$classes = ::get_declared_classes(); $classes = ::array_merge($classes, ::get_declared_interfaces()); $this->classes = ::array_flip($classes); unset($this->classes['NSParser::Parser']); $functions = ::get_defined_functions(); $this->functions = ::array_flip($functions['internal']); if (@::is_file($path)) { $path = ::file_get_contents($path);
Do you really think that's how PHP code should look like - constant
obsessive ::-ing? For my taste, it looks very bad.
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Stanislav Malyshev wrote:
Hi!
$classes = ::get_declared_classes(); $classes = ::array_merge($classes, ::get_declared_interfaces()); $this->classes = ::array_flip($classes); unset($this->classes['NSParser::Parser']); $functions = ::get_defined_functions(); $this->functions = ::array_flip($functions['internal']); if (@::is_file($path)) { $path = ::file_get_contents($path);
Do you really think that's how PHP code should look like - constant
obsessive ::-ing? For my taste, it looks very bad.
Actually, no. The irony of your message is that the code you quote is
used to generate use statements so ::this ::is ::unnecessary.
Greg
Hi!
- functions, constants and classes should use the same resolution
rules. Making special cases just for some of them, or just for user or
just internal ones will lead to confusion.
No, not necessarily. Functions, constants and classes have different
traditional usage patterns in PHP, and as such may have resolution rules
that follow these patterns. I don't say we should look for making them
different, but if necessary, we should use rules that benefit the most
frequent use cases, even if it upsets some purists that like everything
being lined up and painted the same color.
- can someone please explain why is it useful to override internal
function inside a namespace. Isn't it better to explicitly refer to
global functions as global, which would allow compile-time resolution in
a lot more cases than now.
It is not better, since it means that if you have non-namespace code
library, you would have to edit a lot of code to convert it to namespace
- you would have to edit every function call. Aaside form looking
awkward, it is a lot of work. Of course, you can still do it, but you
shouldn't have to.
Since OO libraries rarely use (read: should never use, unless they have
very super-duper good reason to, and even then probably not) global
non-internal functions, usage pattern for internal and non-internal
functions differ.
I know this does not account for the use case of having some function
both internal and implemented in PHP. This is not a frequent use case,
but it happens. I see it much less frequent use case than converting
non-namespaced code to namespaces. In this case, you will have to do
some additional work - you'd have to prepend :: to any global function
you do such tricks on. It's not worse than prepending :: to ALL
functions as you seem to suggest.
globally a PHP-only clone of a missing binary/internal features is a far
more predictable behaviour, and people use it a lot right now. Do we
What do you mean by "predictable"? Current behavior is 100% predictable
and described in the manual.
really want to break this in 5.3?
We don't, and we won't. You'd just need to alter your code slightly to
achieve same behavior. As I understood, the proposal is to force ALL
functions to work this way - how it's better?
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Hi,
No, not necessarily. Functions, constants and classes have different
traditional usage patterns in PHP, and as such may have resolution rules
that follow these patterns. I don't say we should look for making them
different, but if necessary, we should use rules that benefit the most
frequent use cases, even if it upsets some purists that like everything
being lined up and painted the same color.
I find that line of thinking unfortunate. I'm not a purist, if I was, my
list of issues with namespaces would be far longer than it actually is.
Trying to fudge the behavior of PHP to fit some kind of presumed usage
patterns is what got us features like magic quotes and register globals.
Namespaces were introduced to solve the problems of large scale projects,
and I think anyone on this list can agree that on a large scale project,
small hacks like these are a hindrance and not an aid.
globally a PHP-only clone of a missing binary/internal features is a far
more predictable behaviour, and people use it a lot right now. Do weWhat do you mean by "predictable"? Current behavior is 100% predictable
and described in the manual.really want to break this in 5.3?
We don't, and we won't. You'd just need to alter your code slightly to
achieve same behavior. As I understood, the proposal is to force ALL
functions to work this way - how it's better?
Please can you support this with an example? Imagine we introduced
namespaces as they are in 5.0. I want to use json_encode but I have PHP 5.1
and don't have the PECL extension.
What I can do currently is simply drop one of the many PHP-only
json_encode/json_decode replacements and code away without being bothered
whether in my specific environment json_encode/decode is internal or user.
But in namespace context I'll have to do something along the lines of:
if (function_exists('json_encode')) {
$encoded = json_encode($raw);
} else {
$encoded = ::json_encode($raw);
}
I hope this helps to understand the problem. Please correct this code
slightly, as you suggest, to fix this problem.
Regards,
Stan Vassilev
Hi!
Trying to fudge the behavior of PHP to fit some kind of presumed usage
patterns is what got us features like magic quotes and register globals.
You sound like these usage patterns are figment of my imagination. They
are not. As for magic quotes and register globals, I understand that's
very convenient argument to say "your ideas is exactly what lead to
these", only this has nothing to do with current case. Yes, in past in
PHP were some decisions that proved to be wrong later. But bringing them
up in each discussion regardless of its relevance is not going to
convince anybody.
Namespaces were introduced to solve the problems of large scale
projects, and I think anyone on this list can agree that on a large
scale project, small hacks like these are a hindrance and not an aid.
It's not "small hacks". It's supporting the way PHP is used in 100% of
existing code.
internal or user. But in namespace context I'll have to do something
along the lines of:if (function_exists('json_encode')) {
$encoded = json_encode($raw);
} else {
$encoded = ::json_encode($raw);
}
What this code is supposed to do? If it's supposed to call namespace's
json_encode in case global one is not defined, I see nothing wrong with
it. If it is intended to call globally defined json_encode, then you
just write ::json_encode, no ifs. The thing is you'd need to do it
ONLY for json_encode, and only because you want to do something
special with it. The principle is - common case is free, special cases
are available, but for a "fee" - additional code.
--
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
internal or user. But in namespace context I'll have to do something
along the lines of:if (function_exists('json_encode')) {
$encoded = json_encode($raw);
} else {
$encoded = ::json_encode($raw);
}What this code is supposed to do? If it's supposed to call namespace's
json_encode in case global one is not defined, I see nothing wrong with
it. If it is intended to call globally defined json_encode, then you just
write ::json_encode, no ifs. The thing is you'd need to do it ONLY for
json_encode, and only because you want to do something special with it.
The principle is - common case is free, special cases are available, but
for a "fee" - additional code.
The code is supposed to call the global internal json_encode()
if it exists,
and if not, call global user json_encode()
.
Without namespaces this is achieved in the following way:
$encoded = json_encode($raw); // global user or global internal, doesn't
matter, it's transparent.
But inside a namespace, you'll need to do the above checks since internals
and user functions don't resolve under the same rules.
Hi!
But inside a namespace, you'll need to do the above checks since
internals and user functions don't resolve under the same rules.
No, not really - just use:
$encoded = ::json_encode($raw);
It might be even possible to allow something like:
use ::json_encode;
to allow you to write just json_encode()
instead, but this needs to be
checked. ::json_encode just works.
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
is it too late to scrap all this and go with Java/AS3 style
base.package.class please?
Is it too late to switch to Java/AS3? ;)
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Stanislav Malyshev wrote:
is it too late to scrap all this and go with Java/AS3 style
base.package.class please?Is it too late to switch to Java/AS3? ;)
Already have switched front end design to flex3/as3 which is why I'm
asking :o)
Nath