Лямбда-функции в PHP не логичны

Примечание. Я сконструировал эту статью в своей персоне wiki: http://wiki.chacha102.com/Lambda - Наслаждайтесь

У меня возникают проблемы с функциями стиля Lambda в PHP.

Во-первых, This Works:

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

Во-вторых, This Works:

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

В-третьих, это работает:

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

Но это не работает:

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

И это не работает

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

Мой вопрос Почему?, и как я могу заверить, что оба этого:

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

и этот

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

называются правильно? Дополнительные баллы, если вы можете указать документацию о том, почему эта проблема возникает.

Ответы

Ответ 1

Это интересный вопрос. Это работает:

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

но, как вы говорите, этого не происходит:

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

Если вам нужен объект, к которому вы можете динамически вызывать участников, попробуйте:

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));

Но вы не можете заменить вызываемые методы таким образом, потому что __call() вызывается только для методов, которые либо не существуют, либо недоступны (например, они являются частными).

Ответ 2

Функции и методы обрабатываются по-разному в PHP. Вы можете использовать runkit_method_add(), чтобы добавить метод в класс, но я не знаю, как добавить метод к экземпляру.

Ответ 3

Ближе всего вы можете это сделать, используя перегрузку __call, чтобы проверить, содержит ли свойство замыкание:

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

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

Хотя помните следующее примечание из документов:

Анонимные функции в настоящее время реализован с использованием класса Closure. Это деталь реализации и не следует полагаться.

Ссылка: Анонимные функции

Ответ 4

Интересный вопрос, хотя я не вижу причин, почему это должно работать:

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

У меня была аналогичная проблема с __invoke(), и я также не смог ее решить:

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')?

Решение? Никогда не используйте __invoke()!: P

Ответ 5

OP уже представил решение:

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

I.e., любое свойство, содержащее функцию anon, имеет присущую неоднозначность, поскольку добавление скобок после имени свойства сообщает PHP, что вы вызываете метод, а не вызываете функцию anon в свойстве. Чтобы устранить эту двусмысленность, вы должны добавить степень разделения, сначала сохраните свойство в локальной переменной, а затем вызовите функцию anon.

Вам просто нужно посмотреть свойство класса как свойство, а не как "свойство, вызываемое как метод", независимо от его содержимого, и предположить, что php будет искать метод реального класса, если вы поместите скобки после свойство.

Ответ 6

Для меня это похоже на ошибку, а не на "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

Он есть, я просто не думаю, что PHP знает, как его запустить.

Ответ 7

Рассмотрим это:

<?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());

Вы имеете в виду $obj- > foo() замыкание или $obj- > foo() метод? Это неоднозначно, и PHP принимает решение о том, что вы имеете в виду метод. Чтобы использовать закрытие, вы должны устранить, что вы имеете в виду. Один из способов сделать это - использовать временную переменную, как вы это сделали. Другой способ - использовать call_user_func($obj->foo).

Я не знаю другого простого способа.