Does a PHP static anonymous function really work? - php

I'm trying to learn PHP, and now I'm stuck in 'static anonymous function'.
I found this in a tutorial (http://www.slideshare.net/melechi/php-53-part-2-lambda-functions-closures-presentation)
"Object Orientation
Lambda Functions are Closures because they automatically get bound to the scope of the class that they are created in.
'$this' is not always needed in the scope.
Removing '$this' can save on memory.
You can block this behaviour by declaring the Lambda Function as static."
What is wrong with this code?
I get this error:
Parse error: parse error, expecting `T_PAAMAYIM_NEKUDOTAYIM' in C:\wamp\www\z-final\a.php on line 11
Why this code line doesn't work "return static function(){var_dump($this);};" ?
class foo
{
public function getLambda()
{
return function(){var_dump($this);};
}
public function getStaticLambda()
{
return static function(){var_dump($this);};
}
}
$foo = new foo();
$lambda = $foo->getLambda();
$staticLambda = $foo->getStaticLambda();
$lambda();
$staticLambda();

Yes, that is perfectly valid syntax in 5.4+.
Basically, it prevents auto-binding of the current class to the closure (in fact, it prevents all binding, but more on that later).
class Foo {
public function bar() {
return static function() { var_dump($this); };
}
public function baz() {
return function() { var_dump($this); };
}
}
If we instantiate that on 5.4+, the closure bar() returns will have $this set to null. Just as if you made a static call to it. But baz() would have $this set to the foo instance you called baz() on.
So:
$bar = $f->bar();
$bar();
Results in:
Notice: Undefined variable: this in /in/Bpd3d on line 5
NULL
And
$baz = $f->baz();
$baz();
Results in
object(Foo)#1 (0) {
}
Make sense? Great.
Now, what happens if we take closures defined outside of a function:
$a = function() { var_dump($this); };
$a();
We get null (and a notice)
$c = $a->bindTo(new StdClass());
$c();
We get StdClass, just as you'd expect
$b = static function() { var_dump($this); };
$b();
We get null (and a notice)
$d = $b->bindTo(new StdClass());
$d();
This is where things get interesting. Now, we get a warning, a notice, and null:
Warning: Cannot bind an instance to a static closure in /in/h63iF on line 12
Notice: Undefined variable: this in /in/h63iF on line 9
NULL
So in 5.4+, you can declare a static closure, which results in it never getting $this bound to it, nor can you ever bind an object to it...

There should be no need to define it with the static keyword.
<?php
class House
{
public function paint($color)
{
return function() use ($color) { return "Painting the house $color..."; };
}
}
$house = new House();
$callback = $house->paint('red');
var_dump($callback); // object(Closure)#2 (2) {..}
var_dump($callback()); // "Painting the house red..."

Related

PHP function inside another function cannot reference `$this`

I have a function inside a function and the inner function cannot find the outer class. I don't know why. I hope someone has an idea how I can solve this. I don't want to declare the inner function outside of the outer function.
class Foo {
public $test = "test123";
private function outerfunction(){
function innerfunction(){
echo $this->test;
}
innerfunction();
}
}
if I call the inner function I get the error Using $this when not in object context.
just restructure it to an anonymous function:
$innerfunction = function () {
echo $this->test;
}
$innerfunction();
The class context is then automatically bound to the function (see also http://php.net/manual/en/functions.anonymous.php)
Be aware, that this only works for >= PHP 5.4
You have to declare it as a variable, or it will tell you that you aren't in an object context:
$innerfunction = function() {
echo $this->test;
};
call_user_func($innerfunction);
Alternatively, you can keep the function as you do, and pass it the object context since it's known at the call point:
function innerfunction(/* Foo */ $obj) {
echo $obj->test;
};
innerfunction($this);
The most glaring issue with this approach is that calling outerFunction() more than once will produce:
Fatal error: Cannot redeclare innerfunction()...
Another issue is that innerfunction() is declared in the global scope. If this is intentional then that is highly unconventional and you probably should not do that. Anyways, it allows for this:
class Foo {
public $test = "test123";
public function outerfunction(){
function innerfunction(){
//echo $this->test;
}
innerfunction();
}
}
$a = new foo();
// innerfunction(); // NOPE!
$a->outerFunction();
innerfunction(); // YES
So, this is the logical way to declare innerfunction():
class Foo {
public $test = "test123";
public function outerfunction(){
$this->innerfunction();
}
private function innerfunction(){
echo $this->test;
}
}
$a = new foo();
$a->outerFunction();

PHP - Pass a function into another function and call it [duplicate]

I would like to create a Listener class
class Listener {
var $listeners = array();
public function add(callable $function) {
$this->listeners[] = $function;
}
public function fire() {
foreach($this->listeners as $function) {
call_user_func($function);
}
}
}
class Foo {
public function __construct($listener) {
$listener->add($this->bar);
}
public function bar() {
echo 'bar';
}
}
$listener = new Listener();
$foo = new Foo($listener);
But this code fails with this error:
Notice: Undefined property: Foo::$bar in index.php on line 18
Catchable fatal error: Argument 1 passed to Listener::add() must be callable, null given, called in index.php on line 18 and defined index.php on line 5
What am I doing wrong?
Before PHP 5.4, there was no type named callable, so if you use it as a type hint, it means "the class named callable". If you use PHP >= 5.4, callable is a valid hint.
A callable is specified by a string describing the name of the callable (a function name or a class method name for example) or an array where the first element is an instance of an object and the second element is the name of the method to be called.
For PHP < 5.4, replace
public function add(callable $function)
with:
public function add($function)
Call it with:
$listener->add(array($this, 'bar'));
Methods and properties have separate namespaces in PHP, which is why $this->bar evaluates to null: You're accessing an undefined property.
The correct way to create an array in the form of array($object, "methodName"):
Passing the callback correctly:
$listener->add(array($this, 'bar'));
The type hint you have given is okay—as of PHP 5.4, that is.
I don't think you can specify a callable this way...
Try
$listener->add(array($this, 'bar'));
And see http://php.net/manual/en/language.types.callable.php too.

How to remove $this from eval scope?

I am writing simple template engine and stack with problem: $this falls into eval scope even after unseting.
class foo
{
public function method()
{
$code = 'var_dump(isset($this));';
unset($this);
var_dump(isset($this)); // produce: boolean false
eval($code); // produce: boolean true
}
}
$foo = new foo;
$foo ->method();
How to avoid this without modification $code value?
I suggest to create unbound closure, where $this can not be accessed or might be replaced.
NOTE:
Closures are availible since PHP 5.3.0, as #invisal mentioned.
Closure::bindTo availible since PHP 5.4.0.
Anonymous functions in PHP 5.3.0 should not inherit $this from execution context. It was enabled since PHP 5.4.0. Just remove $evUl = $evUl->bindTo(null); from example below for PHP 5.3.0 and it will work as expected.
Example:
<?php
header('Content-Type: text/plain');
class foo {
public function method() {
$code = 'var_dump(isset($this));';
$evUl = function()use($code){ eval($code); };
$evUl = $evUl->bindTo(null);
$evUl();
}
}
$foo = new foo();
$foo->method();
?>
Shows:
bool(false)
$this can be unset as you have described.
Zend_Framework 1.x has an instance where they do this in Zend_Service_WindowsAzure_Storage_Batch class
Note this will not unset the instance, only the reference to the instance within the function scope.
Despite documentation this has been the observed behavior since php 5.1.6.
My only guess on why this won't work with an eval is that it must create a new execution context in which $this is restored into the current scope. This sample exhibits the behavior.
class A {
public function test(){
print "Before Unset\n";
print_r($this);
unset($this);
print "After Unset\n";
print_r($this);
print "Evaled\n";
eval("print_r(\$this);");
print "After Eval\n";
print_r($this);
}
}
$a = new A();
$a->test();
The Output of which is:
Before Unset
A Object
(
)
After Unset
PHP Notice: Undefined variable: this in /home/cgray/q.php on line 9
PHP Stack trace:
PHP 1. {main}() /home/cgray/q.php:0
PHP 2. A->test() /home/cgray/q.php:17
Evaled
A Object
(
)
After Eval
A Object
(
)
One way that you can dodge this issue is to unset $this in the evaluated context.
class foo
{
public function method()
{
$code = 'var_dump(isset($this));';
unset($this);
var_dump(isset($this));
eval("unset(\$this);".$code);
}
$foo = new foo;
$foo->method();
UPDATE
It appears as though includeing a file within the function scope will also restore $this
$this is a pseudo-variable, it cannot be unset.
http://php.net/manual/en/language.oop5.basic.php
It seem like unset($this) will only work within the function scope. I have confirmed with the test.
class foo
{
public function method()
{
unset($this);
var_dump(isset($this));
}
public function method2() {
var_dump(isset($this));
}
}
The $code running within function eval does not run within the function method. Confirmed with the test of $code = 'echo __FUNCTION__;'

Passing an instance method as argument in PHP

I would like to create a Listener class
class Listener {
var $listeners = array();
public function add(callable $function) {
$this->listeners[] = $function;
}
public function fire() {
foreach($this->listeners as $function) {
call_user_func($function);
}
}
}
class Foo {
public function __construct($listener) {
$listener->add($this->bar);
}
public function bar() {
echo 'bar';
}
}
$listener = new Listener();
$foo = new Foo($listener);
But this code fails with this error:
Notice: Undefined property: Foo::$bar in index.php on line 18
Catchable fatal error: Argument 1 passed to Listener::add() must be callable, null given, called in index.php on line 18 and defined index.php on line 5
What am I doing wrong?
Before PHP 5.4, there was no type named callable, so if you use it as a type hint, it means "the class named callable". If you use PHP >= 5.4, callable is a valid hint.
A callable is specified by a string describing the name of the callable (a function name or a class method name for example) or an array where the first element is an instance of an object and the second element is the name of the method to be called.
For PHP < 5.4, replace
public function add(callable $function)
with:
public function add($function)
Call it with:
$listener->add(array($this, 'bar'));
Methods and properties have separate namespaces in PHP, which is why $this->bar evaluates to null: You're accessing an undefined property.
The correct way to create an array in the form of array($object, "methodName"):
Passing the callback correctly:
$listener->add(array($this, 'bar'));
The type hint you have given is okay—as of PHP 5.4, that is.
I don't think you can specify a callable this way...
Try
$listener->add(array($this, 'bar'));
And see http://php.net/manual/en/language.types.callable.php too.

How to access parent object from lambda functions?

I have a recursive lambda function in one of my objects, and it needs to access the object's mysqli connection. This attempt
$recfunc = function($id, $name) use($this) {
Produced an unreasonable fatal error
Fatal error: Cannot use $this as lexical variable in C:\Users\Codemonkey1991\Desktop\workspace\melior\objects\databasemanager.php on line 88
Could anyone give me a few pointers?
Edit: Just to clarify context, I'm trying to create this lambda function inside another function.
Because closures are themselves objects, you need to assign $this to a local variable, like:
$host = $this;
$recfunc = function($id, $name) use ($host) { ...
The reference to $this does not need to be explicitly passed to the lambda function.
class Foo {
public $var = '';
public function bar() {
$func = function() {
echo $this->var;
};
$func();
}
}
$foo = new Foo();
$foo->var = 'It works!';
$foo->bar(); // will echo 'It works!'

Categories