Class variables containing a function in PHP

PHP allows variables to hold functions like this:

$f = function($a,$b) { print "$a $b"; }; $f("Hello","World!"); //prints 'Hello World!' 

This works great for me. I am trying to pass a function to a class and set an instance variable to store this function, but with little luck:

 class Clusterer { private $distanceFunc; public function __construct($f) { $this->distanceFunc = $f; print $f(1,7); //works print $this->distanceFunc(1,7); //exceptions and errors abound } } $func = function($a,$b) { return abs($a-$b); } $c = new Clusterer($func); 

Am I doing something wrong here? The error is that the function does not exist, so I am currently assuming that it is looking for a class function with this name (which does not exist), and then refuses, and not looking for variables ... how can I get it to look at $ this -> distanceFunc as a variable?

EDIT: So, after the recommendations from the answers below, I found a solution that was a function to wrap the call. For example, my class is now:

 class Clusterer { private $distanceFunc; public function __construct($f) { $this->distanceFunc = $f; print $f(1,7); //works print $this->distanceFunc(1,7); //exceptions and errors abound } private function distanceFunc($a,$b) { $holder = $this->distanceFunc; return $holder($a,$b); } } $func = function($a,$b) { return abs($a-$b); } $c = new Clusterer($func); 

and it works great. Php is looking for functions first and can only say if it is a variable in context, I think this is the moral of the story.

+8
php class instance-variables function-pointers
source share
6 answers

PHP does not have first class functions. In JavaScript, if you returned a function, you can do this: myFunctionThatReturnsAFunction()(1,2) , but not in PHP.

 <?php class Clusterer { private $distanceFunc; public function __construct(Closure $f) { $this->distanceFunc = $f; } public function getDistFunc() { return $this->distanceFunc; } } $func = function($a,$b) { return abs($a-$b); }; $c = new Clusterer($func); $a = $c->getDistFunc(); echo $a(1,2); 
+1
source share

Your code does not work because PHP interprets $this->distanceFunc(1,7) as a class method, but you can do the following:

 class Clusterer { private $distanceFunc; public function __construct($f) { $this->distanceFunc = $f; print $f(1,7); //works print call_user_func_array($this->distanceFunc, array(1, 7)); // print $this->distanceFunc(1,7); //exceptions and errors abound } } $func = function($a,$b) { return abs($a-$b); }; $c = new Clusterer($func); 

http://sandbox.onlinephpfunctions.com/code/cdc1bd6bd50f62d5c88631387ac9543368069310

+3
source share

In PHP, methods and properties of an object occupy separate namespaces. This is different from JavaScript, for example, where foo.bar = function() {} is a perfectly acceptable way to define a method.

Therefore, $this->distanceFunc(1,7); Searches for a method named distanceFunc for the current class and the classes it inherits from, but never searches for the property to which you have assigned the same name.

One solution is to get PHP to look for the property and then execute it, for example. $foo = $this->distanceFunc; $foo(1,7) $foo = $this->distanceFunc; $foo(1,7) or call_user_func($this->distanceFunc, 1, 7)

Another would be to define the __call magic method for your class, which runs whenever a non-existent method is referenced. Something like this should work (I have no easy way to check now):

 function __call($func, $args) { if ( property_exists($this, $func) && is_callable($this->$func) ) { return call_user_func_array($this->$func, $args); } } 

Please note that this still does not match the real method, for example, in terms of access to private properties.

+3
source share

It looks like you're going to use the strategy template here. IE Do you want to be able to introduce different methods to calculate distance? If so, there is a more β€œnormal” way to do this.

You can define an interface for the classes that you will use to store the strategy method, ensuring that the class will always have the calculate() method, for example, which will be your distance calculation function. Then, in the constructor of your Clusterer class, enter a test with an interface in the parameter and call the calculation function () for the passed object.

Looks like:

 interface Calculateable { public function calculate(); } class MyDistanceCalculator implements Calculateable { public function calculate() { // Your function here } } class Clusterer { protected $calc; public function __construct(Calculateable $calc) { $this->calc = $calc; $this->calc->calculate(); } } $myClusterer = new Clusterer(new MyDistanceCalculator()); 

Since you defined the interface, any object you pass will have a calculate () function

+2
source share

Take a look at call_user_func

 class Clusterer { private $distanceFunc; public function __construct($f) { $this->distanceFunc = $f; print $f(1,7); //works print call_user_func($this->distanceFunc, 1, 7); //works too ;) } } $func = function($a,$b) { return abs($a-$b); }; $c = new Clusterer($func); 

Don't ask me what the difference is, but it works the way you want (one of the reasons why I hate this language)

+1
source share

In HHVM you can do this:

 <?php class Foo { public function __construct() { $this->bar = function() { echo "Here\n"; }; ($this->bar)(); } } new Foo(); 

But it is not yet supported in PHP. But it will be in PHP 7 ( there will be no release named PHP 6 ).

+1
source share

All Articles