2009-01-07 8 views
7

Me ha gustado la forma de jQuery/Javascript de ampliar la funcionalidad a través de cierres. ¿Es posible hacer algo similar en PHP 5.3?¿Cierres como miembros de la clase?

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 

[edit] mezclé 'it' y 'is' en mi pregunta. heh.

ACTUALIZACIÓN

He descargado 5.3a3 y funciona!

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 
+0

Este es más o menos un duplicado de http://stackoverflow.com/questions/146737/closures-in-php-what-precisely-are-they-and-when-would-you-need-to-use-them – troelskn

+0

Ver también: [Cierre de llamada asignado directamente a la propiedad del objeto] (http: // stackoverflow .com/q/4535330/367456) – hakre

Respuesta

1

PHP puede a través de create_function(). La sintaxis es cruciadora aunque la hace casi completamente inútil.

Parece PHP5.3+ está recibiendo/tiene soporte para closures and lambda's!

Sin embargo también de los RFC

funciones lambda/cierres no son una forma de extender dinámicamente clases por métodos adicionales en tiempo de ejecución. Hay varias otras posibilidades para hacer esto, incluida la semántica _call ya presente.

+0

Supongo que esa cita responde mi pregunta. Me pregunto si las "varias otras posibilidades" incluyen una manera que podría aprovechar la sintaxis de cierre de alguna manera ... – monzee

1

Las funciones/cierres adecuados anónimos solo están disponibles en PHP 5.3, que no estará disponible durante un tiempo - some useful information. No creo que sea realmente posible hacer lo que quieres.

Puede utilizar create_function() en una medida:

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

Sin embargo, el siguiente no funciona como era de esperar:

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

Usted tendría que hacer en su lugar:

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

También no puede usar $this dentro de la función creada beca usar su alcance no sería lo mismo que un método.

Editar

accidentalmente me duplican algunos de respuesta de Martijn como él corrigió su mina mientras estaba escribiendo. Voy a dejar mi respuesta intacta en aras de la exhaustividad

Otra edición

Se podría tal vez también hacer algo como

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

solo cuidado, no lo use en un bucle, ya que comerá memoria. las funciones no se liberan una vez creadas. –

+0

Poner el código entre comillas es ... ugh. Esperaba que los cierres nos dejaran terminar con eso. – monzee

+0

sí, de acuerdo. los cierres en 5.3 te permitirán eliminarlo. –

4

PHP 5.3 permitirá la creación de las funciones lambda y cierres de lo cual le permite implementar su ejemplo de código. Desde el PHP RFC:

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

La función se devuelve como referencia y se puede pasar como una devolución de llamada.Por ejemplo, la creación de una función:

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

Pasar una función lambda como una devolución de llamada:

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

Los cierres también permite importar las variables del ámbito local:

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

Donde $ search y $ replacement se declaran fuera del cierre, y $ text es el parámetro de la función.

+0

¡Cuidado, no defines el "mundo"! espacio de nombres

+0

Sí, he leído que los cierres vendrán en PHP 5.3, solo me pregunto si podemos asignarlo a un miembro de la clase. – monzee

+0

Los cierres y lambdas son referencias a la función (asignación de funciones). Puede asignarlo a cualquier cosa que desee. –

3

Puede aprovechar el método __call para redirigir llamadas a métodos inexistentes. Agregue un registro a su clase para que pueda agregar/eliminar métodos sobre la marcha. La convención de llamadas para la función externa es que el primer parámetro es el objeto al que se ha vinculado la función.

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

} 

función que devuelve clone_object matriz de objetos clonados imaginar. El primer parámetro es el objeto que se clonará, y el segundo es el número de clones.

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

Ahora, de esta manera se puede inyectar método clone_object en la clase Foo y darle un nombre de alias bar:

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

En la última línea método bar llamando hará que la redirección funcione clone_object. Tenga en cuenta que la convención de llamadas insertará el parámetro $this en la lista de parámetros, por lo que (5) se cambiará a ($this, 5).

La advertencia es que las funciones externas no pueden funcionar en propiedades y métodos privados/protegidos.

Una última palabra, si puede pensar en una solución que no cambie objetos sobre la marcha, probablemente debería usarla. Lo anterior es una magia negra y debe usarse con extrema precaución.

+0

Gracias, eso es un fragmento interesante. – monzee

0

Utilizando la función de crear usted podría hacer algo como:

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

El código de clase contendría algo como:

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

Nota: que usted no será capaz de utilizar el ámbito de los objetos ($this variable) en su función de creación, ya que básicamente esto solo está usando una interfaz de objeto para crear_función que está en el espacio de nombres global. Sin embargo, puede agregar fácilmente una instancia del objeto a la lista de parámetros y colocarla en la clase, pero de nuevo solo tendría acceso a los métodos públicos de la clase activada.

0

Desde PHP5.4 de liberación que puede hacer aún más:

  1. dinámicamente añadir sus métodos a los objetos
  2. Uso $ este dentro de estos métodos
  3. Heredar un objeto dinámico de otro

La idea básica se implementa aquí: https://github.com/ptrofimov/jslikeobject

0

Yo uso este create_closure() en mi trabajo para separar las devoluciones de llamada en Clases:

<?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}));};"; 
     } 
?> 

ejemplo:

<?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) 
            {//... 
            } 
           ); 
     } 
?> 
Cuestiones relacionadas