Hi!
- I am a little confused about the OOP interaction. How does a function
become a public method of the class?
To clarify: the "public method" ist just the internal representation of
the lambda function and has *nothing* to do with the semantics of
calling the lambda itself. The "method" only means that the lambda
function defined inside another method can access the class members and
"public" only means that the lambda function can still be called from
outside the class.
If one knew how to access it, which it seems is not possible/feasible for
user-space code.
No, that's not what I meant. The engine uses the following internal trick:
a) Upon copmilation, my patch simply adds the lambdas as normal
functions to the function table with an automatically generated
unique (!) name. If it happens to be defined within a class method,
the function will be added as a public final method to that class.
b) That added function is not directly callable due to checks of a flag
in the internal structure of that function.
c) At the place of the function definition the compiler leaves an
opcode "grab function $generatedname and make a closure out of it".
This opcode then looks up the generated lambda function, copies the
function structure, saves the bound variables in that structure and
returns the copied structure as a resource.
d) Normally, when a function is called, the name is looked up in the
function table. The function structure that is retrieved from there
is then used to execute the function. Since a lambda resource is
already a function structure, there is no necessity to look up
anything in the function table but the function structure can be
directly passed on to the executor.
Please note step d): The closure functionality only changes the *lookup*
of the function - so instead of getting the function structure from a
hash table lookup I get the function structure by retrieving it from the
resource. But *after* the lookup of a class method there are checks for
the access protection of that method. So these access protection checks
also apply to closures that were called. If a lambda function was not
declared public, it could not be used outside of the class it was
defined in. Perhaps this makes it clearer?
I see. It would be great if you could update the RFC with this information so
that it's clearer.
Done: http://wiki.php.net/rfc/closures
Two other questions that just occurred to me:
1) What is the interaction with namespaces, if any? Are lambdas as
implemented here ignorant of namespace, or do they take the namespace where
they are lexically defined?
My patch itself is namespace-ignorant, but the net result is not:
a) The generated internal function names do not contain the current
namespace name, but since namespace names in function names are only
used for lookup if you want to call the function. And calling
lambdas by name (!) directly doesn't work anyway (is not supposed
to work) so this poses no problem.
b) The code *inside* the closure is namespace-aware because the
information of which namespace is used is added at compile time.
Either the name lookup is done entirely at compile time or the
current compiler namespace is automatically added to all runtime
lookup calls (this is already the case with current code). So
the information which namespace a function resides in is currently
*irrelevant* at runtime when calling other functions.
For (b) let me make two examples:
Suppose you have the following code:
namespace Foo;
function baz () {
return "Hello World!\n";
}
function bar () {
return function () {
echo baz ();
};
}
and in another file:
$lambda = Foo::bar ();
$lambda ();
This will - as expected - print "Hello World!\n".
The reason is that the compiler upon arriving at the baz() function call
inside the closure already looks up the function in the function table
directly (it knows the current namespace) - and simply creates a series
of opcodes that will call the function with the name "Foo::baz" (the
lookup is already done at compile time).
Consider this other code:
foo-bar.php:
namespace Foo;
function bar () {
return function () {
echo baz ();
};
}
foo-baz.php:
namespace Foo;
function baz () {
return "Hello World!\n";
}
baz.php:
function baz () {
return "PHP!\n";
}
test1.php:
require 'foo-bar.php';
require 'foo-baz.php';
$lambda = Foo::bar ();
$lambda ();
test2.php:
require 'foo-bar.php';
require 'baz.php';
$lambda = Foo::bar ();
$lambda ();
Running test1.php yields "Hello World!" whereas running test2.php yields
"PHP!". Why is this? Because when the compiler reaches the baz ()
function call in the closure, it cannot find the function so it cannot
determine whether it's a function in global or in namespace scope. So it
will simply add a series of opcodes that say "try Foo::bar and if that
does not exist try bar". Here again, Foo:: is added by the compiler to
the opcode because the compiler has the necessary information which
namespace the call is currently in.
The runtime execution engine NEVER looks at the function as to which
namespace the function belongs to, it ONLY looks at the function calling
opcodes that the compiler already correctly (!) generates.
Please note that I have only described what the PHP engine does with
namespaces until now ANYWAY, the closures DO NOT CHANGE ANYTHING related
to this. That's why my patch doesn't have to care about namespaces at
all but the net-result closures will.
2) What happens with the following code?
class Foo {
private $a;
}
$f = new Foo();
$b = 5;
$f->myfunc = function($c) {
lexical $b;
print $a; // This generates an error, no?
print $b; // This prints 5, right?
print $c; // Should print whatever $c is.
}
$f->myfunc(3);
Or is the above a parse error entirely?
Well, first of all, it's a parse error because you forgot the ; after
the } of the closure. ;-)
Then: Closures only have access to the scope in which they are defined
in. Whether you assign that closure to a class property or not does not
change the semantics of the closure itself. The closure definition is
simply the following part: function ($c) { ... }. You then use the part
to store the closure as a class property. The closure you define is
entirely oblivious of the class since it is not defined inside a class
method. Consider this code:
function foo () {
echo $a;
}
$f->myfunc = 'foo';
Well, of course, first of all $a will not work anyway because $a has
never been the object member access method of PHP. But even if you use
$this->a, that won't work with foo either. For the exact same reasons it
will not work with closures.
Your comments inside the closure are correct though: $a gives an error
(notice: undefined variable), $b gives 5 (unless $b is modified later
on) and $c gives the current value of the parameter $c.
But: As I already said last time, $f->myfunc(3) won't work. That will
give "no such method". For the same reason as $f->myfunc = 'foo';
$f->myfunc(); will not work - because -> can only be used to call
METHODS, not dynamic functions stored in properties.
Regards,
Christian
--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php