SunshinePHP 2019

Callback / Callable 类型

自 PHP 5.4 起可用 callable 类型指定回调类型 callback。本文档基于同样理由使用 callback 类型信息。

一些函数如 call_user_func()usort() 可以接受用户自定义的回调函数作为参数。回调函数不止可以是简单函数,还可以是对象的方法,包括静态类方法。

传递

PHP是将函数以string形式传递的。 可以使用任何内置或用户自定义函数,但除了语言结构例如:array()echoempty()eval()exit()isset()list()printunset()

一个已实例化的 object 的方法被作为 array 传递,下标 0 包含该 object,下标 1 包含方法名。 在同一个类里可以访问 protected 和 private 方法。

静态类方法也可不经实例化该类的对象而传递,只要在下标 0 中包含类名而不是对象。自 PHP 5.2.3 起,也可以传递 'ClassName::methodName'

除了普通的用户自定义函数外,也可传递 匿名函数 给回调参数。

Example #1 回调函数示例

<?php 

// An example callback function
function my_callback_function() {
    echo 
'hello world!';
}

// An example callback method
class MyClass {
    static function 
myCallbackMethod() {
        echo 
'Hello World!';
    }
}

// Type 1: Simple callback
call_user_func('my_callback_function'); 

// Type 2: Static class method call
call_user_func(array('MyClass''myCallbackMethod')); 

// Type 3: Object method call
$obj = new MyClass();
call_user_func(array($obj'myCallbackMethod'));

// Type 4: Static class method call (As of PHP 5.2.3)
call_user_func('MyClass::myCallbackMethod');

// Type 5: Relative static class method call (As of PHP 5.3.0)
class {
    public static function 
who() {
        echo 
"A\n";
    }
}

class 
extends {
    public static function 
who() {
        echo 
"B\n";
    }
}

call_user_func(array('B''parent::who')); // A

// Type 6: Objects implementing __invoke can be used as callables (since PHP 5.3)
class {
    public function 
__invoke($name) {
        echo 
'Hello '$name"\n";
    }
}

$c = new C();
call_user_func($c'PHP!');
?>

Example #2 使用 Closure 的示例

<?php
// Our closure
$double = function($a) {
    return 
$a 2;
};

// This is our range of numbers
$numbers range(15);

// Use the closure as a callback here to 
// double the size of each element in our 
// range
$new_numbers array_map($double$numbers);

print 
implode(' '$new_numbers);
?>

以上例程会输出:

2 4 6 8 10

Note:

在函数中注册有多个回调内容时(如使用 call_user_func()call_user_func_array()),如在前一个回调中有未捕获的异常,其后的将不再被调用。

add a note add a note

User Contributed Notes 14 notes

up
199
steve at mrclay dot org
6 years ago
Performance note: The callable type hint, like is_callable(), will trigger an autoload of the class if the value looks like a static method callback.
up
204
andrewbessa at gmail dot com
6 years ago
You can also use the $this variable to specify a callback:

<?php
class MyClass {

    public
$property = 'Hello World!';

    public function
MyMethod()
    {
       
call_user_func(array($this, 'myCallbackMethod'));
    }

    public function
MyCallbackMethod()
    {
        echo
$this->property;
    }

}
?>
up
169
Riikka K
3 years ago
A note on differences when calling callbacks as "variable functions" without the use of call_user_func() (e.g. "<?php $callback = 'printf'; $callback('Hello World!') ?>"):

- Using the name of a function as string has worked since at least 4.3.0
- Calling anonymous functions and invokable objects has worked since 5.3.0
- Using the array structure [$object, 'method'] has worked since 5.4.0

Note, however, that the following are not supported when calling callbacks as variable functions, even though they are supported by call_user_func():

- Calling static class methods via strings such as 'foo::doStuff'
- Calling parent method using the [$object, 'parent::method'] array structure

All of these cases are correctly recognized as callbacks by the 'callable' type hint, however. Thus, the following code will produce an error "Fatal error: Call to undefined function foo::doStuff() in /tmp/code.php on line 4":

<?php
class foo {
    static function
callIt(callable $callback) {
       
$callback();
    }
   
    static function
doStuff() {
        echo
"Hello World!";
    }
}

foo::callIt('foo::doStuff');
?>

The code would work fine, if we replaced the '$callback()' with 'call_user_func($callback)' or if we used the array ['foo', 'doStuff'] as the callback instead.
up
189
edanschwartz at gmail dot com
3 years ago
You can use 'self::methodName' as a callable, but this is dangerous. Consider this example:

<?php
class Foo {
    public static function
doAwesomeThings() {
       
FunctionCaller::callIt('self::someAwesomeMethod');
    }

    public static function
someAwesomeMethod() {
       
// fantastic code goes here.
   
}
}

class
FunctionCaller {
    public static function
callIt(callable $func) {
       
call_user_func($func);
    }
}

Foo::doAwesomeThings();
?>

This results in an error:
Warning: class 'FunctionCaller' does not have a method 'someAwesomeMethod'.

For this reason you should always use the full class name:
<?php
FunctionCaller
::callIt('Foo::someAwesomeMethod');
?>

I believe this is because there is no way for FunctionCaller to know that the string 'self' at one point referred to to `Foo`.
up
178
computrius at gmail dot com
5 years ago
When specifying a call back in array notation (ie. array($this, "myfunc") ) the method can be private if called from inside the class, but if you call it from outside you'll get a warning:

<?php

class mc {
   public function
go(array $arr) {
      
array_walk($arr, array($this, "walkIt"));
   }

   private function
walkIt($val) {
       echo
$val . "<br />";
   }

    public function
export() {
        return array(
$this, 'walkIt');
    }
}

$data = array(1,2,3,4);

$m = new mc;
$m->go($data); // valid

array_walk($data, $m->export()); // will generate warning

?>

Output:
1<br />2<br />3<br />4<br />
Warning: array_walk() expects parameter 2 to be a valid callback, cannot access private method mc::walkIt() in /in/tfh7f on line 22
up
167
metamarkers at gmail dot com
5 years ago
you can pass an object as a callable if its class defines the __invoke() magic method..
up
152
Yzmir Ramirez
4 years ago
> As of PHP 5.2.3, it is also possible to pass 'ClassName::methodName'

You can also use 'self::methodName'.  This works in PHP 5.2.12 for me.
up
77
mariano dot REMOVE dot perez dot rodriguez at gmail dot com
3 years ago
I needed a function that would determine the type of callable being passed, and, eventually,
normalized it to some extent. Here's what I came up with:

<?php

/**
* The callable types and normalizations are given in the table below:
*
*  Callable                        | Normalization                   | Type
* ---------------------------------+---------------------------------+--------------
*  function (...) use (...) {...}  | function (...) use (...) {...}  | 'closure'
*  $object                         | $object                         | 'invocable'
*  "function"                      | "function"                      | 'function'
*  "class::method"                 | ["class", "method"]             | 'static'
*  ["class", "parent::method"]     | ["parent of class", "method"]   | 'static'
*  ["class", "self::method"]       | ["class", "method"]             | 'static'
*  ["class", "method"]             | ["class", "method"]             | 'static'
*  [$object, "parent::method"]     | [$object, "parent::method"]     | 'object'
*  [$object, "self::method"]       | [$object, "method"]             | 'object'
*  [$object, "method"]             | [$object, "method"]             | 'object'
* ---------------------------------+---------------------------------+--------------
*  other callable                  | idem                            | 'unknown'
* ---------------------------------+---------------------------------+--------------
*  not a callable                  | null                            | false
*
* If the "strict" parameter is set to true, additional checks are
* performed, in particular:
*  - when a callable string of the form "class::method" or a callable array
*    of the form ["class", "method"] is given, the method must be a static one,
*  - when a callable array of the form [$object, "method"] is given, the
*    method must be a non-static one.
*
*/
function callableType($callable, $strict = true, callable& $norm = null) {
  if (!
is_callable($callable)) {
    switch (
true) {
      case
is_object($callable):
       
$norm = $callable;
        return
'Closure' === get_class($callable) ? 'closure' : 'invocable';
      case
is_string($callable):
       
$m    = null;
        if (
preg_match('~^(?<class>[a-z_][a-z0-9_]*)::(?<method>[a-z_][a-z0-9_]*)$~i', $callable, $m)) {
          list(
$left, $right) = [$m['class'], $m['method']];
          if (!
$strict || (new \ReflectionMethod($left, $right))->isStatic()) {
           
$norm = [$left, $right];
            return
'static';
          }
        } else {
         
$norm = $callable;
          return
'function';
        }
        break;
      case
is_array($callable):
       
$m = null;
        if (
preg_match('~^(:?(?<reference>self|parent)::)?(?<method>[a-z_][a-z0-9_]*)$~i', $callable[1], $m)) {
          if (
is_string($callable[0])) {
            if (
'parent' === strtolower($m['reference'])) {
              list(
$left, $right) = [get_parent_class($callable[0]), $m['method']];
            } else {
              list(
$left, $right) = [$callable[0], $m['method']];
            }
            if (!
$strict || (new \ReflectionMethod($left, $right))->isStatic()) {
             
$norm = [$left, $right];
              return
'static';
            }
          } else {
            if (
'self' === strtolower($m['reference'])) {
              list(
$left, $right) = [$callable[0], $m['method']];
            } else {
              list(
$left, $right) = $callable;
            }
            if (!
$strict || !(new \ReflectionMethod($left, $right))->isStatic()) {
             
$norm = [$left, $right];
              return
'object';
            }
          }
        }
        break;
    }
   
$norm = $callable;
    return
'unknown';
  }
 
$norm = null;
  return
false;
}

?>

Hope someone else finds it useful.
up
5
bradyn at NOSPAM dot bradynpoulsen dot com
2 years ago
When trying to make a callable from a function name located in a namespace, you MUST give the fully qualified function name (regardless of the current namespace or use statements).

<?php

namespace MyNamespace;

function
doSomethingFancy($arg1)
{
   
// do something...
}

$values = [1, 2, 3];

array_map('doSomethingFancy', $values);
// array_map() expects parameter 1 to be a valid callback, function 'doSomethingFancy' not found or invalid function name

array_map('MyNamespace\doSomethingFancy', $values);
// => [..., ..., ...]
up
2
whysteepy at gmail dot com
1 year ago
Another Appearance of Callbacks! Here is one way of them - methods of an instantiated object can be callable and implemented as variable functions without php's default functions that can call user-defined callback functions.

class Test {
    protected $items = array();

    public function __construct()
    {
        $this->items[] = array($this, 'callBackOne');
        $this->items[] = array($this, 'callBackTwo');
    }

    public function callBackOne()
    {
        echo __METHOD__ . ' has been called as a callback.';
    }

    public function callBackTwo()
    {
        echo __METHOD__ . ' has been called as a callback.';
    }   

    public function getItems()
    {
        return $this->items;
    }
}

$o = new Test();
$itemLists = $o->getItems();

foreach ($itemLists as $itemList) {

        // call each one as a variable function
        echo '<pre>';
        print_r($itemList());
        echo '</pre>';
}

// Outputs the following
// Test::callBackOne has been called as a callback.

// Test::callBackTwo has been called as a callback.
up
3
pawel dot tadeusz dot niedzielski at gmail dot com
2 years ago
@edanschwartz at gmail dot com

You can use ::class property to always indicate the class you're in when using static methods:

<?php
class Foo {
    public static function
doAwesomeThings() {
       
FunctionCaller::callIt(self::class . '::someAwesomeMethod');
    }

    public static function
someAwesomeMethod() {
       
// fantastic code goes here.
   
}
}

class
FunctionCaller {
    public static function
callIt(callable $func) {
       
call_user_func($func);
    }
}

Foo::doAwesomeThings();
?>
up
0
chris dot rutledge at gmail dot com
13 hours ago
Having read this line in the manual above,

"A method of an instantiated object is passed as an array containing an object at index 0 and the method name at index 1. Accessing protected and private methods from within a class is allowed."

I decided to do some testing to see if I could access private methods using the call_user_func methods. Thankfully not, but for completeness here is my test which also covers using static and object contexts

<?php
class foo {
   
    public static
$isInstance = false;
   
    public function
__construct() {
       
self::$isInstance = true;
    }

    public function
bar() {
       
var_dump(self::$isInstance);
        echo
__METHOD__;
    }
   
    private function
baz() {
       
var_dump(self::$isInstance);
        echo
__METHOD__;
    }
   
    public function
qux() {
       
$this->baz();
    }
   
    public function
quux() {
       
self::baz();
    }
}

call_user_func(['foo','bar']);    //fase, foo:bar

call_user_func(['foo','baz']);  //warning, cannot access private method

call_user_func(['foo','quux']); //false, foo::baz

call_user_func(['foo','qux']);  //fatal, Using $this when not in object context

$foo = new foo;

call_user_func([$foo,'bar']);    //true, foo::bar
call_user_func([$foo,'baz']);    //warning, cannot access private method
call_user_func([$foo,'qux']);    //true, foo::baz

call_user_func(['foo','bar']);  //true, foo::bar (static call, yet $isInstance is true)

?>
up
0
Daniel Klein
1 year ago
You can use "self::method_name", "static::method_name" and "parent::method_name" in callables:

<?php
class StaticCallable {
    public static function
foo($values) {
        return
array_map('self::bar', $values);
    }

    public static function
bar($value) {
        return
"{$value}: 42";
    }

    public static function
baz($values) {
        return
array_map('static::qux', $values);
    }

    public static function
qux($value) {
        return
"{$value}: 123";
    }
}

class
StaticExtension extends StaticCallable {
    public static function
bar($value) {
        return
"{$value}: Marvin the Paranoid Android";
    }

    public static function
qux($value) {
        return
"{$value}: Zaphod Beeblebrox";
    }
}

print_r(StaticCallable::foo([1, 2, 3]));
print_r(StaticExtension::foo([1, 2, 3]));

print_r(StaticCallable::baz([1, 2, 3]));
print_r(StaticExtension::baz([1, 2, 3]));
?>

Results:
Array
(
    [0] => 1: 42
    [1] => 2: 42
    [2] => 3: 42
)
Array
(
    [0] => 1: 42
    [1] => 2: 42
    [2] => 3: 42
)
Array
(
    [0] => 1: 123
    [1] => 2: 123
    [2] => 3: 123
)
Array
(
    [0] => 1: Zaphod Beeblebrox
    [1] => 2: Zaphod Beeblebrox
    [2] => 3: Zaphod Beeblebrox
)

"self::" uses the same class as the called method, "static::" uses the same class as the called class, and "parent::" (not shown) uses the parent class, or generates a warning if there is no parent.
up
-3
Dan J
9 months ago
You can avoid repeating a long namespace for classes in callable arrays by making use of the "use" operator and the special "::class" constant.

Documentation of use operator:
http://php.net/manual/en/language.namespaces.importing.php

Documentation of ::class constant:
http://php.net/manual/en/language.oop5.constants.php

<?php
// Library file with namespace My\Library\Namespace
require 'MyLibrary.php';

// Alias for SortingClass
use \My\Library\Namespace\SortingClass;

// Callable array referring to SortingClass::SortFunction
$callable = [SortingClass::class, 'SortFunction'];

$values = [3, 1, 2];
usort($values, $callable);
To Top