How can I invoke a ReflectionFunction wrapping a closure that utilizes $this? - php

This is easiest to explain with an example:
class Example {
private $x;
public $f;
public function __construct() {
$this->x = 10;
$this->f = function() {
return $this->x;
};
}
}
$ex = new Example();
$f = new ReflectionFunction($ex->f);
echo $f->invoke().PHP_EOL;
Running this results in an error:
PHP Fatal error: Uncaught Error: Using $this when not in object context
That's because I've used $this in the closure, so it's really more like a ReflectionMethod, but ReflectionMethod doesn't seem to want to take a closure as an argument, so I'm not really sure what I can do.
How can I invoke $ex->f using reflection?

Well, I really don't know why that behavior is happening. But there's a workaround (well, I found it after a few tests).
As PHP doesn't let you explicit the bind of $this (it's bound automatically), you have to use an alternative variable:
$t = $this;
$this->f = function() use ($t) {
return $t->x;
};
The entire code:
class Example {
private $x;
public $f;
public function __construct() {
$this->x = 10;
$t = $this;
$this->f = function() use ($t) {
return $t->x;
};
}
}
$ex = new Example();
$f = new ReflectionFunction($ex->f);
echo $f->invoke().PHP_EOL;
And the result wanted
10
Tested on PHP 5.4, 5.5, 5.6 and 7.
UPDATE
After #mpen answer, I've realized about his restrictions and the use of Reflection.
When you use a ReflectionFunction to invoke a function, which is a closure at least, you should treat that as a closure. ReflectionFunction has a method called ReflectionFunction::getClosure().
The Class remains as #mpen created and the use will be as:
$ex = new Example();
$f = new ReflectionFunction($ex->f);
$closure = $f->getClosure();
echo $closure().PHP_EOL;
But only works on PHP 7.
For PHP 5.4, 5.5 and 5.6 you'll have to bind the class and scope. Weird, but it's the only way that I found using Closure::bindTo() or Closure::bind():
$ex = new Example();
$f = new ReflectionFunction($ex->f);
$closure = $f->getClosure();
$class = $f->getClosureThis();
$closure = $closure->bindTo($class , $class);
echo $closure().PHP_EOL;
Or just:
$ex = new Example();
$f = new ReflectionFunction($ex->f);
$class = $f->getClosureThis();
$closure = Closure::bind($f->getClosure() , $class , $class);
echo $closure().PHP_EOL;
It's very important to pass the class as scope (second parameter) which will determine whether you can access private/protected variable or not.
The second paramater also could be the class name as:
$closure = $closure->bindTo($class , 'Example');//PHP >= 5.4
$closure = $closure->bindTo($class , get_class($class));//PHP >= 5.4
$closure = $closure->bindTo($class , Example::class);//PHP 5.5
But I didn't concerned about performance, so the class passed twice is just fine to me.
There's also the method Closure::call() which can be used to change the scope, but also just for PHP >= 7.

Related

Is there any cleaner way to call a clouse property in PHP [duplicate]

I would like to be able to call a closure that I assign to an object's property directly without reassigning the closure to a variable and then calling it. Is this possible?
The code below doesn't work and causes Fatal error: Call to undefined method stdClass::callback().
$obj = new stdClass();
$obj->callback = function() {
print "HelloWorld!";
};
$obj->callback();
As of PHP7, you can do
$obj = new StdClass;
$obj->fn = function($arg) { return "Hello $arg"; };
echo ($obj->fn)('World');
or use Closure::call(), though that doesn't work on a StdClass.
Before PHP7, you'd have to implement the magic __call method to intercept the call and invoke the callback (which is not possible for StdClass of course, because you cannot add the __call method)
class Foo
{
public function __call($method, $args)
{
if(is_callable(array($this, $method))) {
return call_user_func_array($this->$method, $args);
}
// else throw exception
}
}
$foo = new Foo;
$foo->cb = function($who) { return "Hello $who"; };
echo $foo->cb('World');
Note that you cannot do
return call_user_func_array(array($this, $method), $args);
in the __call body, because this would trigger __call in an infinite loop.
You can do this by calling __invoke on the closure, since that's the magic method that objects use to behave like functions:
$obj = new stdClass();
$obj->callback = function() {
print "HelloWorld!";
};
$obj->callback->__invoke();
Of course that won't work if the callback is an array or a string (which can also be valid callbacks in PHP) - just for closures and other objects with __invoke behavior.
As of PHP 7 you can do the following:
($obj->callback)();
Since PHP 7 a closure can be called using the call() method:
$obj->callback->call($obj);
Since PHP 7 is possible to execute operations on arbitrary (...) expressions too (as explained by Korikulum):
($obj->callback)();
Other common PHP 5 approaches are:
using the magic method __invoke() (as explained by Brilliand)
$obj->callback->__invoke();
using the call_user_func() function
call_user_func($obj->callback);
using an intermediate variable in an expression
($_ = $obj->callback) && $_();
Each way has its own pros and cons, but the most radical and definitive solution still remains the one presented by Gordon.
class stdKlass
{
public function __call($method, $arguments)
{
// is_callable([$this, $method])
// returns always true when __call() is defined.
// is_callable($this->$method)
// triggers a "PHP Notice: Undefined property" in case of missing property.
if (isset($this->$method) && is_callable($this->$method)) {
return call_user_func($this->$method, ...$arguments);
}
// throw exception
}
}
$obj = new stdKlass();
$obj->callback = function() { print "HelloWorld!"; };
$obj->callback();
It seems to be possible using call_user_func().
call_user_func($obj->callback);
not elegant, though.... What #Gordon says is probably the only way to go.
Well, if you really insist. Another workaround would be:
$obj = new ArrayObject(array(),2);
$obj->callback = function() {
print "HelloWorld!";
};
$obj['callback']();
But that's not the nicest syntax.
However, the PHP parser always treats T_OBJECT_OPERATOR, IDENTIFIER, ( as method call. There seems to be no workaround for making -> bypass the method table and access the attributes instead.
I know this is old, but I think Traits nicely handle this problem if you are using PHP 5.4+
First, create a trait that makes properties callable:
trait CallableProperty {
public function __call($method, $args) {
if (property_exists($this, $method) && is_callable($this->$method)) {
return call_user_func_array($this->$method, $args);
}
}
}
Then, you can use that trait in your classes:
class CallableStdClass extends stdClass {
use CallableProperty;
}
Now, you can define properties via anonymous functions and call them directly:
$foo = new CallableStdClass();
$foo->add = function ($a, $b) { return $a + $b; };
$foo->add(2, 2); // 4
well, it should be emphisized that storing the closure in a variable, and call the varible is actually (wierdly) faster, depending on the call amount, it becomes quite a lot, with xdebug (so very precise measuring), we are talking about 1,5 (the factor, by using a varible, instead of directly calling the __invoke. so instead , just store the closure in a varible and call it.
Here's another alternative based on the accepted answer but extending stdClass directly:
class stdClassExt extends stdClass {
public function __call($method, $args)
{
if (isset($this->$method)) {
$func = $this->$method;
return call_user_func_array($func, $args);
}
}
}
Usage example:
$foo = new stdClassExt;
$foo->blub = 42;
$foo->whooho = function () { return 1; };
echo $foo->whooho();
You are probably better off using call_user_func or __invoke though.
Updated:
$obj = new stdClass();
$obj->callback = function() {
print "HelloWorld!";
};
PHP >= 7 :
($obj->callback)();
PHP >= 5.4 :
$callback = $obj->callback;
$callback();
If you're using PHP 5.4 or above you could bind a callable to the scope of your object to invoke custom behavior. So for example if you were to have the following set up..
function run_method($object, Closure $method)
{
$prop = uniqid();
$object->$prop = \Closure::bind($method, $object, $object);
$object->$prop->__invoke();
unset($object->$prop);
}
And you were operating on a class like so..
class Foo
{
private $value;
public function getValue()
{
return $this->value;
}
}
You could run your own logic as if you were operating from within the scope of your object
$foo = new Foo();
run_method($foo, function(){
$this->value = 'something else';
});
echo $foo->getValue(); // prints "something else"
I note that this works in PHP5.5
$a = array();
$a['callback'] = function() {
print "HelloWorld!";
};
$a['callback']();
Allows one to create a psuedo-object collection of closures.

Callable and closures

I've created not very complex test code (tested in PHP 5.5.12):
<?php
class Test
{
private $cached = null;
public function __construct()
{
$this->cached = [];
$this->cached[0] = 12;
}
function wrap($function, $index)
{
if (isset($this->cached[$index])) {
return $this->cached[$index];
}
$result = call_user_func($function);
return $result;
}
}
class B
{
public function run()
{
$x = 6;
$obj = new Test();
$value = $obj->wrap(
function () use ($x) {
return $this->test($x);
},
1
);
echo $value."<br />";
}
protected function test($x)
{
echo "I'm running ";
return $x * $x;
}
}
class C extends B
{
public function run()
{
$x = 6;
$obj = new Test();
$myFunc = function () use ($x) {
return $this->test($x);
};
$value = $obj->wrap($myFunc, 1);
echo $value."<br />";
}
}
class D extends B
{
public function run()
{
$x = 6;
$obj = new Test();
$value = $obj->wrap(array($this, 'test'), 1);
echo $value."<br />";
}
}
$b = new B();
$b->run();
$c = new C();
$c->run();
$d = new D();
$d->run();
Probably there are some parts of the code you could say it could be done better but the main point are closures function and callable. Those classes simulate in a very simple way caching system. If data is in cache it returns data from cache otherwise function that gets data is called (of course this cache system doesn't work because it doesn't have to - it's just a sample code).
Questions:
1) Why when using object $d I get the following warning:
call_user_func() expects parameter 1 to be a valid callback, cannot access protected method D::test()
and is it possible to launch protected method from parent? When I change this method from protected to public it can be launched without a problem
2) As you probably noticed I want to use some arguments for function I call using call_user_sync. Unfortunately I don't know those parameters when I call call_user_func so in class B and C I used closures where I can use/pass extra parameters. I have 2 extra questions connected to this:
is it the way where closures are useful and commonly used?
is it possible using object $d to pass parameters to test method without using closures but not when calling call_user_sync but inside class D?
It is important to note scope at the time of the execution. You are creating the callback in the correct scope, but you are executing the callback in another object with no access to the protected method (the callback get's executed in class Test not in a parent or child of class B.
I ran in to this issue some time ago when writing my own dispatcher class. One option was to set a "parent" on the dispatcher, and pass the dispatcher as one of the parameters on the callback. The callback then checks the "parent" associated with the Dispatcher for === $this, and then knows that it has access and goes to town.
You have to do your own access checking, is the point.

Use php callable to call constructor

I am trying to call a class' constructor through a callable, so I had the following code:
$callable = array('Foo', '__construct');
However calling this will throw the following error:
Fatal error: Non-static method Foo::__construct() cannot be called statically
I understand that the constructor is not a static method, but I can't use an existing instance to call the constructor for a new instance (as it will just call the constructor on the existing object again), is there any way at all to call a constructor like this?
If you're looking for a simple way to dynamically choose which class to construct, you can use a variable name with the new keyword, like so:
$inst = new $class_name;
// or, if the constructor takes arguments, provide those in the normal way:
$inst = new $class_name('foo', 'bar');
However, if what you need is a way of passing the constructor to something which is already expecting a callable, the best I can think of is to wrap it in an anonymous function:
$callable = function() { return new Foo; }
call_user_func( $callable );
Or using the short single-expression closure syntax introduced in PHP 7.4:
$callable = fn() => new Foo;
call_user_func( $callable );
If you really have to use call_user_func, this might work, though it's not clear why you would want to do this:
$reflection = new ReflectionClass("Foo");
$instance = $reflection->newInstanceWithoutConstructor();
call_user_func(array($instance, '__construct'));
The more correct answer, as #MauganRa already mentioned in a comment, is using \ReflectionClass::newInstance() or \ReflectionClass::newInstanceArgs(). I think this should be expressed in a full answer.
Now if you want to pass this around as a callback and use call_user_func() or call_user_func_array(), try this:
$callable = [new \ReflectionClass('C'), 'newInstance'];
Or a more fleshed-out example:
class Car {
private $color;
private $size;
public function __construct($color, $size) {
$this->color = $color;
$this->size = $size;
}
public function describe() {
return "A $this->size $this->color car.";
}
}
$callable = [new \ReflectionClass('Car'), 'newInstance'];
$cars = [];
$cars[] = call_user_func($callable, 'red', 'big')->describe();
$cars[] = call_user_func_array($callable, ['blue', 'small'])->describe();
var_export($cars);
Demo: https://3v4l.org/7fvQL
I am not aware of another / better solution.

Get class constructor

how to get a class constructor function name without instantiating the class?
example:
$class = 'someClass';
$constructor = somehow get constructor;
$args = array();
$object = call_user_func_array(array($class,$constructor),$args);
what I need is to create a object by passing a undetermined number of variables into it's constructor.
You can do this with Reflection:
<?php
class Pants
{
public function __construct($a, $b, $c)
{
$this->a = $a;
$this->b = $b;
$this->c = $c;
}
}
$className = 'pants';
$class = new ReflectionClass($className);
$obj = $class->newInstanceArgs(array(1, 2, 3));
var_dump($obj);
This will also work if your constructor uses the old style (unless your code makes use of namespaces and you are using PHP 5.3.3 or, presumably, greater, as old-style constructors will no longer work with namespaced code - more info):
<?php
class Pants {
function Pants($a, $b, $c) { ... }
}
If the class has no constructor and you wish to use reflection, use $class->newInstance() instead of $class->newInstanceArgs(...). To do this dynamically, it would look like this:
$object = null === $class->getConstructor()
? $class->newInstance()
: $class->newInstanceArgs($args)
;
The function name of the constructor in PHP is always __construct, so you have it already without having to do anything.

Reference to static method in PHP?

In PHP, I am able to use a normal function as a variable without problem, but I haven't figured out how to use a static method. Am I just missing the right syntax, or is this not possible?
(EDIT: the first suggested answer does not seem to work. I've extended my example to show the errors returned.)
function foo1($a,$b) { return $a/$b; }
class Bar
{
static function foo2($a,$b) { return $a/$b; }
public function UseReferences()
{
// WORKS FINE:
$fn = foo1;
print $fn(1,1);
// WORKS FINE:
print self::foo2(2,1);
print Bar::foo2(3,1);
// DOES NOT WORK ... error: Undefined class constant 'foo2'
//$fn = self::foo2;
//print $fn(4,1);
// DOES NOT WORK ... error: Call to undefined function self::foo2()
//$fn = 'self::foo2';
//print $fn(5,1);
// DOES NOT WORK ... error: Call to undefined function Bar::foo2()
//$fn = 'Bar::foo2';
//print $fn(5,1);
}
}
$x = new Bar();
$x->UseReferences();
(I am using PHP v5.2.6 -- does the answer change depending on version too?)
PHP handles callbacks as strings, not function pointers. The reason your first test works is because the PHP interpreter assumes foo1 as a string. If you have E_NOTICE level error enabled, you should see proof of that.
"Use of undefined constant foo1 - assumed 'foo1'"
You can't call static methods this way, unfortunately. The scope (class) is relevant so you need to use call_user_func instead.
<?php
function foo1($a,$b) { return $a/$b; }
class Bar
{
public static function foo2($a,$b) { return $a/$b; }
public function UseReferences()
{
$fn = 'foo1';
echo $fn(6,3);
$fn = array( 'self', 'foo2' );
print call_user_func( $fn, 6, 2 );
}
}
$b = new Bar;
$b->UseReferences();
In php 5.2, you can use a variable as the method name in a static call, but to use a variable as the class name, you'll have to use callbacks as described by BaileyP.
However, from php 5.3, you can use a variable as the class name in a static call. So:
class Bar
{
public static function foo2($a,$b) { return $a/$b; }
public function UseReferences()
{
$method = 'foo2';
print Bar::$method(6,2); // works in php 5.2.6
$class = 'Bar';
print $class::$method(6,2); // works in php 5.3
}
}
$b = new Bar;
$b->UseReferences();
?>
You could use the full name of static method, including the namespace.
<?php
function foo($method)
{
return $method('argument');
}
foo('YourClass::staticMethod');
foo('Namespace\YourClass::staticMethod');
The name array array('YourClass', 'staticMethod') is equal to it. But I think the string may be more clear for reading.
In PHP 5.3.0, you could also do the following:
<?php
class Foo {
static function Bar($a, $b) {
if ($a == $b)
return 0;
return ($a < $b) ? -1 : 1;
}
function RBar($a, $b) {
if ($a == $b)
return 0;
return ($a < $b) ? 1 : -1;
}
}
$vals = array(3,2,6,4,1);
$cmpFunc = array('Foo', 'Bar');
usort($vals, $cmpFunc);
// This would also work:
$fooInstance = new Foo();
$cmpFunc = array('fooInstance', 'RBar');
// Or
// $cmpFunc = array('fooInstance', 'Bar');
usort($vals, $cmpFunc);
?>
Coming from a javascript background and being spoiled by it, I just coded this:
function staticFunctionReference($name)
{
return function() use ($name)
{
$className = strstr($name, '::', true);
if (class_exists(__NAMESPACE__."\\$className")) $name = __NAMESPACE__."\\$name";
return call_user_func_array($name, func_get_args());
};
}
To use it:
$foo = staticFunctionReference('Foo::bar');
$foo('some', 'parameters');
It's a function that returns a function that calls the function you wanted to call. Sounds fancy but as you can see in practice it's piece of cake.
Works with namespaces and the returned function should work just like the static method - parameters work the same.
This seems to work for me:
<?php
class Foo{
static function Calc($x,$y){
return $x + $y;
}
public function Test(){
$z = self::Calc(3,4);
echo("z = ".$z);
}
}
$foo = new Foo();
$foo->Test();
?>
In addition to what was said you can also use PHP's reflection capabilities:
class Bar {
public static function foo($foo, $bar) {
return $foo . ' ' . $bar;
}
public function useReferences () {
$method = new ReflectionMethod($this, 'foo');
// Note NULL as the first argument for a static call
$result = $method->invoke(NULL, '123', 'xyz');
}
}
"A member or method declared with static can not be accessed with a variable that is an instance of the object and cannot be re-defined in an extending class"
(http://theserverpages.com/php/manual/en/language.oop5.static.php)

Categories