14

Note: I have condensed this article into my person wiki: http://wiki.chacha102.com/Lambda - Enjoy

I am having some troubles with Lambda style functions in PHP.

First, This Works:

$foo = function(){ echo "bar"; };
$foo();

Second, This Works:

class Bar{
    public function foo(){
       echo "Bar";
    }

Third, This works:

$foo = new stdClass;
$foo->bar = function(){ echo "bar"; };
$test = $foo->bar;
$test();

But, this does not work:

$foo = new stdClass;
$foo->bar = function(){ echo "bar"; };
$foo->bar();

And, this does not work

class Bar{
    public function foo(){
       echo "Bar";
    }
$foo = new Bar;
$foo->foo = function(){ echo "foo"; };
$foo->foo(); // echo's bar instead of Foo.

My Question is Why?, and how can I assure that both this:

$foo->bar = function(){ echo "test"; };
$foo->bar();

and this

$foo = new Bar;
$foo->bar();

are called properly? Extra Points if you can point to documentation stating why this problem occurs.

Tyler Carter
  • 60,743
  • 20
  • 130
  • 150
  • This has more to do with how you cannot override methods of classes dynamically in PHP which is time and time again a source of frustration for me. I end up using arrays (or one of many other "solutions") to dynamically build "things" containing data and functions. Arrays don't work well for auto completion and you lose a lot of other functionality. It's been 10 years since you asked, nothing has changed significantly. If you want to redefine methods after object instantiation you have to use properties instead (ie. you simply can't redefine methods), and properties must be called differently. – Joel M Feb 06 '20 at 19:43

7 Answers7

9

This is an interesting question. This works:

$a = new stdClass;
$a->foo = function() { echo "bar"; };
$b = $a->foo;
$b(); // echos bars

but as you say this doesn't:

$a = new stdClass;
$a->foo = function() { echo "bar"; };
$a->foo();

If you want an object to which you can dynamically call members, try:

class A {
  public function __call($func, $args) {
    $f = $this->$func;
    if (is_callable($f)) {
      return call_user_func_array($f, $args);
    }
  }
}

$a = new A;
$a->foo = function() { echo "bar\n"; };
$a->foo2 = function($args) { print_r($args); };
$a->foo();
$a->foo2(array(1 => 2, 3 => 4));

But you can't replace callable methods this way because __call() is only called for methods that either don't exist or aren't accessible (eg they're private).

cletus
  • 616,129
  • 168
  • 910
  • 942
  • This along with other comments let me set up a `stdObj` class that I can use to reliably create a object without a class (kind of). Thanks! – Tyler Carter Jan 17 '10 at 16:53
3

Functions and methods are treated differently in PHP. You can use runkit_method_add() to add a method to a class, but I know of no way to add a method to an instance.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
1

The closest you could get to make this happen would be by using the __call overload to check if a property contains a closure:

class what {
  function __call($name, $args) {
    $f= $this->$name;
    if ($f instanceof Closure) {
      $f();
    }
  }
}

$foo = new what();
$foo->bar = function(){ echo "bar"; };
$foo->bar();

Though bear in mind the following note from the docs:

Anonymous functions are currently implemented using the Closure class. This is an implementation detail and should not be relied upon.

Reference: Anonymous functions

NullUserException
  • 83,810
  • 28
  • 209
  • 234
leepowers
  • 37,828
  • 23
  • 98
  • 129
  • Another option would to use the `is_callable` statement. I'm not sure if that will tell me if it is a closure though. – Tyler Carter Jan 17 '10 at 07:19
  • This still won't quite work - if there's a method declaration for `foo()` the `__call()` won't be called. – leepowers Jan 17 '10 at 07:21
  • I'm not too concerned about overriding a method. I'll come back in the morning, this code might work. Would just have to create a class like `stdCallableObj` that would act as a `stdClass` when I want to assign closures to it. – Tyler Carter Jan 17 '10 at 07:23
1

Interesting question although I see no reason why this should work:

class Bar{
    public function foo(){
       echo "Bar";
    }
$foo = new Bar;
$foo->foo = function(){ echo "foo"; };
$foo->foo(); // echo's bar instead of Foo.

I had a similar problem with __invoke(), and I've also not been able to solve it:

class base {
    function __construct() {
        $this->sub = new sub();
    }

    function __call($m, $a) {
    }
}

class sub {
    function __invoke($a) {
    }
}

$b = new base();
$b->sub('test'); // should trigger base::__call('sub', 'test') or sub::__invoke('test')?

Solution? Never use __invoke()! :P

Alix Axel
  • 151,645
  • 95
  • 393
  • 500
1

The OP has already presented the solution:

$foo = new stdClass;
$foo->bar = function(){ echo "bar"; };
$test = $foo->bar;
$test();

I.e., any property that contains an anon function has an inherent ambiguity because adding parenthesis after the property name tells PHP that you are calling a method, and not invoking an anon function in a property. To resolve this ambiguity, you must add a degree of separation by storing the property into a local variable first, and then invoking the anon function.

You just have to look at the class property as a property instead of as "a property callable as a method" no matter what it's contents are and assume that php is going to look for a real class method if you put parenthesis after the property.

Alfred
  • 21,058
  • 61
  • 167
  • 249
0

To me it seems like a bug rather than a "quirk":

<?php
$t = new stdClass;
$t->a = function() { echo "123"; };
var_dump($t);
echo "<br><br>";
$x = (array)$t;
var_dump($x);
echo "<br><br>";
$x["a"]();
echo "<br><br>";
?>

object(stdClass)#1 (1) { ["a"]=> object(Closure)#2 (0) { } } 

array(1) { ["a"]=> object(Closure)#2 (0) { } } 

123

It is there, I just don't think that PHP knows how to run it.

Natalie Adams
  • 1,841
  • 22
  • 27
0

Consider this:

<?php

class A {

    public $foo

    public function foo() {
        return 'The method foo';
    }

}

$obj = new A;
$obj->foo = function() { return 'The closure foo'; };

var_dump($obj->foo());

Do you mean $obj->foo() the closure or $obj->foo() the method? It is ambiguous and PHP makes the decision that you mean the method. To use the closure, you have to disambiguate what you mean. One way you can do this is by using a temporary variable as you have done. Another way is to use call_user_func($obj->foo).

I do not know of any other easy way.

erisco
  • 14,154
  • 2
  • 40
  • 45