Pass class method to a closure in PHP (similar to JS) - php

In Javascript you can do this:
// Define a function
function logIt(a, b) { console.log(a, b); }
function somethingElse() {
// Store it in a variable
var theLog = logIt;
// Call it somewhere else *as the variable-name*
asyncCall(function() { theLog(1, 2); });
}
What I'd like to do in PHP is this:
class A
{
// Define a simple class method
protected function echoIt($a, $b) {
echo $a, $b;
}
public function doSomething(array $things) {
$theEchoFunction = $this->echoIt; // save it for the next line
// Get into a closure and pass the method as a variable
array_map(function($thing) use ($theEchoFunction) { // <-- this is the bit I'd like to do
// Call the function directly from the variable
$theEchoFunction($thing[0], $thing[1]);
}, $things);
}
}
I know that it's easy enough to just do $that = $this; then pass $that through to the closure, but that means I can't access $that->echoIt because it's protected. Is it possible to send the method itself to the closure?
This question could actually be the X/Y problem I guess. What I want to do is call the protected method from inside the closure. I'd like to just pass the method so that the closure doesn't need to know that the class has an echoIt method.

Specifically, this will do just fine* (in PHP as in Javascript):
class A
{
protected function echoIt($a, $b) {
echo $a, $b;
}
public function doSomething(array $things) {
array_map(function ($thing) {
$this->echoIt($thing[0], $thing[1]);
}, $things);
}
}
Assuming that is just a test setup and you do need to pass the callback around in a variable, the way to do that is with a callable pseudo type:
class A
{
protected function echoIt($a, $b) {
echo $a, $b;
}
public function doSomething(array $things) {
$callback = [$this, 'echoIt'];
array_map(function ($thing) use ($callback) {
$callback($thing[0], $thing[1]);
}, $things);
}
}
* Since PHP 5.4.

class Test
{
protected function echoIt($a, $b) {
echo $a, $b;
}
public function doSomething(array $things) {
$theEchoFunction = function($a, $b) {
return $this->echoIt($a, $b);
};
array_map(function($thing) use ($theEchoFunction) {
$theEchoFunction($thing[0], $thing[1]);
}, $things);
}
}
$test = new Test();
$test->doSomething(["1", "2"]);
Result
12
This works for me, I don't know if it works as you expected. But to assign the method to a variable, you'd need to make the variable callable. Thus made me think that you can create an anonymous function that is wrapper over the protected method. Then you pass that function to the closure.

Related

Class variables holding a function in PHP

PHP allows for variables to hold functions like so:
$f = function($a,$b) {
print "$a $b";
};
$f("Hello","World!"); //prints 'Hello World!'
This works just fine for me. I'm trying to pass a function into a class and set an instance variable to hold that 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 doesn't exist so my guess currently is that it looks for a class function with that name (which there isn't one) and then gives up rather than looking for variables as well... how can I make it view the $this->distanceFunc as a variable?
EDIT:
So after the advice from the answers below, I found a solution which was the make a function to wrap the invocation. 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 this works great. Php looks for functions first and can only tell if it is a variable by context I guess is the moral of this story.
Your code doesn't 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
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 valid way of defining a method.
Consequently, $this->distanceFunc(1,7); looks for a method named distanceFunc on the current class, and the classes it inherits from, but never looks for the property which you happen to have given the same name.
One solution is to force PHP to look up a property, then execute it, e.g. $foo = $this->distanceFunc; $foo(1,7) or call_user_func($this->distanceFunc, 1, 7)
Another would be to define the magic method __call on your class, which gets run whenever a non-existent method is referenced. Something like this ought to work (I don't have an easy way to testright now):
function __call($func, $args) {
if ( property_exists($this, $func) && is_callable($this->$func) ) {
return call_user_func_array($this->$func, $args);
}
}
Note that this still isn't the same as a real method, for instance in terms of access to private properties.
It looks like you're going for a strategy pattern here. IE you want to be able to inject different methods for calculating distance? If so there is a more "sane" way to do it.
You can define an interface to the classes you will use to store the strategy method ensuring that the class will always have the method calculate() for example which would be your distance calculation function. Then in the constructor of your Clusterer class, type check against the interface in the parameter and call calculate() on the object passed in.
Looks like this:
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());
Because you defined an interface, any object you pass in will have the calculate() function
In HHVM, you can do this:
<?php
class Foo
{
public function __construct()
{
$this->bar = function() { echo "Here\n"; };
($this->bar)();
}
}
new Foo();
But it's not yet supported in PHP. But, it will be in PHP 7 (there will be no release named PHP 6).
PHP doesn't have first class functions. In JavaScript if you returned a function you could 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);
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 is the difference, but it works the way you want (One of the reasons i hate this language)

Call static function from variable

I have the following setup:
class test {
public static function something() {
$somethingElseFunction = "somethingElse";
// How can I call the method with the name saved in variable?
}
public static function somethingElse($a) {
echo 'bla';
}
}
How can I call the function using the variable? (the function name is in variable).
Also I need to do a function_exists() for it.
Tried this:
if (function_exists(self::$somethingElseFunction ())) {
if (!call_user_func(self::$somethingElseFunction , $a)) {
}
}
Didn't work.
In PHP>=5.4 you can use just self:: de-reference:
self::$somethingElseFunction();
-but in earlier versions that will cause error (because it wasn't allowed to use dynamic static methods de-reference). So then you can always use such things as call_user_func_array() :
class test {
public static function something() {
$somethingElseFunction = "somethingElse";
call_user_func_array(array(__CLASS__, $somethingElseFunction), array("bla"));
}
public static function somethingElse($a) {
var_dump($a);
}
}
test::something();
-this will work for PHP>=5.0
About function_exists() call - it expects string as parameter, thus I recommend to use method_exists() - because that function is intended to do the stuff:
public static function something() {
$somethingElseFunction = "somethingElse";
if(method_exists(__CLASS__, $somethingElseFunction))
{
call_user_func_array(array(__CLASS__, $somethingElseFunction), array("bla"));
}
}
You should be able to use the following:
test::$somethingElseFunction();
Use this function:
$classname = 'somethingElse';
call_user_func('test::' . $classname, $params);

it is possible to assign a function to a class variable at runtime in php?

it is possible to assign to a class variable a function at runtime to be executed? a kind of "function pointer" like C
something like this: (this won't work because sum is out of the scope of A, but this is the pattern i mean)
class A {
public $function_name;
public functon run($arg1,$arg2){
$function_name($arg1,$arg2);
}
}
function sum($a,$b){
echo $a+$b;
}
$a=new A();
$a->function_name='sum';
$a->run();
[edit]
i know there is "call_user_func" but it need as i understand to have the function in the scope or use a public class method
You could use an anonymous function if you use PHP >5.3.0:
$sum = function($a, $b) {
return $a+$b;
}
$a->function_name = $sum;
Using call_user_func_array:
<?php
class A {
public $function_name;
public function run($arg1,$arg2){
return call_user_func_array( $this->function_name, array($arg1, $arg2 ) );
}
}
function sum($a,$b){
return $a+$b;
}
$a=new A();
$a->function_name= 'sum';
var_dump( $a->run(1,1) ); //2
?>
It works regardless of scope. You just gotta call it using call_user_func. I also fixed a couple of typos in your example.
<?php
class A {
public $function_name;
public function run($arg1, $arg2) {
call_user_func($this->function_name, $arg1, $arg2);
}
}
function sum($a, $b){
echo $a + $b;
}
$a = new A();
$a->function_name = 'sum';
$a->run(2, 3);
?>
Live example
Another way is to make use variable variables (applicable to object method)
public static function sum($arg1, $arg2)
{
..
}
public function run($arg1, $arg2)
{
$func = $this->function_name;
$func( $arg1, $arg2); <-- procedural call
self::$func($arg1, $arg2); <-- static method call
}
Use any variation of the Callback pseudo type.
Use it with call_user_func or call_user_func_array
The manual gives great examples of usage for the above.
Also see the new php 5.4 Closure::bindTO method if you want to be able to easily use the $this keyword in it.

Handle requests to several classes within the same PHP SOAP server

Is that possible to have a single PHP SOAP server which will handle requests to several classes (services)?
If yes, could you please show an example implementation?
If not, could you please describe why?
Could you wrap the other services in a single class? Completely untested, it was just a thought...
class MySoapService
{
public function __construct()
{
$this->_service1 = new Service1();
$this->_service2 = new Service2();
}
// You could probably use __call() here and intercept any calls,
// thus avoiding the need for these declarations in the wrapper class...
public function add($a, $b)
{
return $this->_service1->add($a, $b);
}
public function sub($a, $b)
{
return $this->_service2->sub($a, $b);
}
}
class Service1
{
public function add($a, $b)
{
return $a + $b;
}
}
class Service2
{
public function sub($a, $b)
{
return $a - $b;
}
}
Another take on the same general idea (proxy class) - for php5
Uses a hash to map functions to callbacks.
class ServiceProxy {
private $map = array();
public function addMethod($name, $callback) {
if(is_callable($callback)) {
$this->map[$name] = $callback;
return true;
}
return false;
}
function __call($name, $args) {
if(isset($map[$name])) {
return call_user_func_array($map[$name], $args);
} else {
return null;
}
}
}
This class could also use the reflection API and add all pulic methods from an object, for example.
If both classes had methods with identical names (but different parameters) then you could use func_get_args() to analyze the arguments and differentiate between the methods that way.
If they take the same arguments... then you're kinda stuck.
Why can't you just use two separate Web Services?

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