Замыкания как ученики? - PullRequest
       11

Замыкания как ученики?

7 голосов
/ 07 января 2009

Мне понравился способ расширения функциональности с помощью замыканий в jQuery / Javascript. Возможно ли сделать что-то подобное в PHP 5.3?

class Foo
{
    public $bar;
}

$foo = new Foo;
$foo->bar = function($baz) { echo strtoupper($baz); };
$foo->bar('lorem ipsum dolor sit amet');
// LOREM IPSUM DOLOR SIT AMET

[править] перепутал «это» и «есть» в моем вопросе. хех.

UPDATE

Я скачал 5.3a3, и он работает!

class Foo
{
    protected $bar;
    public $baz;

    public function __construct($closure)
    {
        $this->bar = $closure;
    }

    public function __call($method, $args)
    {
        $closure = $this->$method;
        call_user_func_array($closure, $args);
    }

}

$foo = new Foo(function($name) { echo "Hello, $name!\n"; });
$foo->bar('Mon'); 
// Hello, Mon!
$foo->baz = function($s) { echo strtoupper($s); };
$foo->baz('the quick brown fox jumps over the lazy dog');
// THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG

Ответы [ 7 ]

4 голосов
/ 07 января 2009

PHP 5.3 позволит создавать лямбда-функции и замыкания, которые позволят вам реализовать пример кода. Из PHP RFC :

function & (parameters) use (lexical vars) { body }

Функция возвращается как ссылка и может быть передана как обратный вызов. Например, создание функции:

$lambda = function () { echo "Hello World!\n"; };

Передача лямбда-функции в качестве обратного вызова:

function replace_spaces ($text) {
    $replacement = function ($matches) {
        return str_replace ($matches[1], ' ', ' ').' ';
    };
    return preg_replace_callback ('/( +) /', $replacement, $text);
}

Замыкания также позволяют импортировать переменные из локальной области:

$map = function ($text) use ($search, $replacement) {
    if (strpos ($text, $search) > 50) {
       return str_replace ($search, $replacement, $text);
    } else {
        return $text;
    }
};

Где $ search и $ replace объявлены вне замыкания, а $ text является параметром функции.

3 голосов
/ 07 января 2009

Вы можете воспользоваться методом __call для перенаправления несуществующих вызовов методов. Добавьте реестр в свой класс, чтобы вы могли добавлять / удалять методы на лету. Соглашение о вызове для внешней функции заключается в том, что первым параметром является объект, с которым связана функция.

clas Foo {

    private $registry;

    public function __call($alias, $args) {
        if (array_key_exists($alias, $this->registry)) {
            // prepend list of parameters with $this object
            array_unshift($args, $this);
            return call_user_func_array($this->registry[$alias], $args)
        }
    }

    public function register($method, $alias = null) {
        $alias or $alias = $method; // use original method name if no alias
        $this->registry[$alias] = $method;
    }

    public function unregister($alias) {
        unset($this->registry[$alias]);
    }

}

Представьте себе функцию clone_object, которая возвращает массив клонированных объектов. Первый параметр - это объект, который нужно клонировать, а второй - количество клонов.

function clone_object($foo, $n) {
    $a = array();
    while ($n--) {
        $a[] = clone $foo;
    }
    return $a;
}

Теперь, таким образом, вы можете внедрить метод clone_object в класс Foo и дать ему псевдоним bar:

$f = new Foo();
$f->register('clone_object', 'bar');
$f->bar(5); // this will return array of 5 cloned objects

В последней строке вызов метода bar вызовет перенаправление на функцию clone_object. Помните, что соглашение о вызовах будет вставлять параметр $this в список параметров, поэтому (5) будет изменено на ($this, 5).

Предупреждение: внешние функции не могут работать с закрытыми / защищенными свойствами и методами.

Просто последнее слово, если вы можете придумать решение, которое не меняет объекты на лету, вы, вероятно, должны его использовать. Выше приведено чёрное волшебство, и его следует использовать с особой осторожностью.

1 голос
/ 07 января 2009

Правильные анонимные функции / замыкания доступны только в PHP 5.3, который некоторое время не будет широко доступен - некоторая полезная информация . Я не думаю, что действительно возможно делать то, что вы хотите.

Вы можете использовать create_function() до такой степени:

$func = create_function('$baz', ' echo strtoupper($baz); ');
$func("hello");

Однако следующее не работает так, как вы ожидаете:

$foo = new Foo;
$foo->bar = create_function('$baz', ' echo strtoupper($baz); ');
$foo->bar('lorem ipsum dolor sit amet');

Вместо этого вам нужно будет сделать:

call_user_func($foo->bar, 'lorem ipsum dolor sit amet');

Также вы не можете использовать $this внутри созданной функции, поскольку ее область действия не будет такой же, как у метода.

Редактировать

Я случайно продублировал часть ответа Мартейна, когда он редактировал его, пока я писал свой. Я оставлю свой ответ без изменений ради полноты

Другое редактирование

Вы могли бы также сделать что-то вроде

class Foo
{
    public $bar;

    protected $anonFuncs = array();

    public function create_method($name, $params, $code) {
        if (strlen($params)) $params .= ',';
        $params .= '$self';
        $this->anonFuncs[$name] = create_function($params, $code);
    }

    public function __call($name, $args) {
        if (!isset($this->anonFuncs[$name])) {
            trigger_error("No method $name", E_USER_ERROR);
        }

        $args[] = $this;

        return call_user_func_array($this->anonFuncs[$name], $args);
    }
}

$foo = new Foo;
$foo->create_method('bar', '$baz', ' echo strtoupper($baz) . get_class($self); ');
$foo->bar('lorem ipsum dolor sit amet');
1 голос
/ 07 января 2009

PHP может через create_function () . Синтаксис жесток, хотя делает его практически бесполезным.

Кажется, PHP5.3 + получает / поддерживает замыкания и лямбда-выражения!

Однако также из RFC

Лямбда-функции / замыкания не являются способом динамического расширения классов дополнительными методами во время выполнения. Для этого есть несколько других возможностей, включая уже существующую семантическую _ _call.

0 голосов
/ 10 июня 2014

Я использую этот create_closure () в своей работе для разделения обратных вызовов на классы:

<?php
function create_closure($fun, $args, $uses)
         {$params=explode(',', $args.','.$uses);
          $str_params='';
          foreach ($params as $v)
                  {$v=trim($v, ' &$');
                   $str_params.='\''.$v.'\'=>&$'.$v.', ';
                  }
          return "return function({$args}) use ({$uses}) {{$fun}(array({$str_params}));};";
         }
?>

пример:

<?php
$loop->addPeriodicTimer(1, eval(create_closure('pop_message', '$timer', '$cache_key, $n, &$response, &$redis_client')));

function pop_message($params)
         {extract($params, EXTR_REFS);
          $redis_client->ZRANGE($cache_key, 0, $n)
                            ->then(//normal
                                   function($data) use ($cache_key, $n, &$timer, &$response, &$redis_client)
                                   {//...
                                   },
                                   //exception
                                   function ($e) use (&$timer, &$response, &$redis_client)
                                   {//...
                                   }
                                  );
         }
?>
0 голосов
/ 10 января 2013

С момента выпуска PHP5.4 вы можете сделать еще больше:

  1. Динамически добавьте ваши методы к объектам
  2. Используйте $ this внутри этих методов
  3. Наследовать один динамический объект от другого

Основная идея реализована здесь: https://github.com/ptrofimov/jslikeobject

0 голосов
/ 08 января 2009

Используя функцию создания, вы можете сделать что-то вроде:

$Dynamic->lambda = create_function('$var', 'return $var." world."; ');
$classic = $Dynamic->lambda("hello"); // classic would contain "hello world."

Код класса будет содержать что-то вроде:

public function __set($methodname, $function)
{
    $this->methods[$methodname] = $function;
}

public function __get($methodname)
{
    return $this->methods[$methodname];
}

public function __call($method, $args)
{
   $funct = $this->{$method};
   //$args[] = $this; --to add the current object as a parameter option
   return call_user_func_array($funct, $args);
}

Примечание: вы не сможете использовать область объекта (переменная $this) в вашей функции create, поскольку в основном это просто использование интерфейса объекта для create_function, которая находится в глобальном пространстве имен. Однако вы можете легко добавить экземпляр объекта в список параметров и перетащить его в класс, но опять же у вас будет доступ только к открытым методам этого класса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...