回调/可调用函数

回调可以使用 callable 类型声明表示。

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

传递

PHP 函数通过其名称作为 string 传递。可以使用任何内置或用户定义的函数,除了语言结构,例如: array()echoempty()eval()exit()isset()list()printunset()

已实例化的 object 的方法作为 array 传递,该数组在索引 0 处包含一个 object,在索引 1 处包含方法名称。允许从类内部访问受保护和私有方法。

静态类方法也可以在不实例化该类的 object 的情况下传递,方法是,在索引 0 处传递类名而不是 object,或传递 'ClassName::methodName'

除了常见的用户定义函数外,匿名函数箭头函数 也可以传递给回调参数。

注意:

从 PHP 8.1.0 开始,匿名函数也可以使用 一等可调用语法 创建。

通常,任何实现 __invoke() 的对象也可以传递给回调参数。

示例 #1 回调函数示例

<?php

// 回调函数示例
function my_callback_function() {
echo
'hello world!';
}

// 回调方法示例
class MyClass {
static function
myCallbackMethod() {
echo
'Hello World!';
}
}

// 类型 1:简单回调
call_user_func('my_callback_function');

// 类型 2:静态类方法调用
call_user_func(array('MyClass', 'myCallbackMethod'));

// 类型 3:对象方法调用
$obj = new MyClass();
call_user_func(array($obj, 'myCallbackMethod'));

// 类型 4:静态类方法调用
call_user_func('MyClass::myCallbackMethod');

// 类型 5:相对静态类方法调用
class A {
public static function
who() {
echo
"A\n";
}
}

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

call_user_func(array('B', 'parent::who')); // A,从 PHP 8.2.0 开始已弃用

// 类型 6:实现 __invoke 的对象可以用作可调用函数
class C {
public function
__invoke($name) {
echo
'Hello ', $name, "\n";
}
}

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

示例 #2 使用闭包的回调示例

<?php
// 我们的闭包
$double = function($a) {
return
$a * 2;
};

// 这是我们的数字范围
$numbers = range(1, 5);

// 在这里使用闭包作为回调
// 使我们范围内的每个元素大小加倍
$new_numbers = array_map($double, $numbers);

print
implode(' ', $new_numbers);
?>

上面的例子将输出

2 4 6 8 10

注意:

如果之前有回调抛出未捕获的异常,则使用诸如 call_user_func()call_user_func_array() 等函数注册的回调将不会被调用。

添加注释

用户贡献注释 12 个注释

277
andrewbessa at gmail dot com
12 年前
您也可以使用 $this 变量来指定回调

<?php
class MyClass {

public
$property = 'Hello World!';

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

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

}
?>
193
computrius at gmail dot com
10 年前
在指定数组符号中的回调时(例如 array($this, "myfunc")),如果从类内部调用,该方法可以是私有的,但是如果从外部调用,您会收到警告

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

?>

输出
1<br />2<br />3<br />4<br />
警告:array_walk() 期望参数 2 为有效回调,无法访问私有方法 mc::walkIt() in /in/tfh7f on line 22
208
steve at mrclay dot org
11 年前
性能说明:可调用类型提示(如 is_callable())将触发类的自动加载,如果该值看起来像一个静态方法回调。
189
Riikka K
9 年前
关于在不使用 call_user_func() 的情况下将回调作为“变量函数”调用时的差异说明(例如:“<?php $callback = 'printf'; $callback('Hello World!') ?>”)

- 使用函数名作为字符串至少从 4.3.0 版本开始就可行
- 调用匿名函数和可调用对象从 5.3.0 版本开始就可行
- 使用数组结构 [$object, 'method'] 从 5.4.0 版本开始就可行

然而,请注意,即使 call_user_func() 支持以下情况,在将回调作为变量函数调用时,它们并不支持

- 通过字符串(例如 'foo::doStuff')调用静态类方法
- 使用 [$object, 'parent::method'] 数组结构调用父方法

然而,所有这些情况都被 'callable' 类型提示正确识别为回调。因此,以下代码将产生错误“致命错误:在 /tmp/code.php 的第 4 行调用未定义的函数 foo::doStuff()”

<?php
class foo {
static function
callIt(callable $callback) {
$callback();
}

static function
doStuff() {
echo
"Hello World!";
}
}

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

如果我们用 'call_user_func($callback)' 替换 '$callback()',或者如果我们使用数组 ['foo', 'doStuff'] 作为回调,代码将正常工作。
189
edanschwartz at gmail dot com
9 年前
您可以使用 'self::methodName' 作为可调用对象,但这很危险。请考虑以下示例

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

public static function
someAwesomeMethod() {
// 这里有很棒的代码。
}
}

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

Foo::doAwesomeThings();
?>

这将导致错误
警告:类 'FunctionCaller' 没有方法 'someAwesomeMethod'。

因此,您应该始终使用完整的类名
<?php
FunctionCaller
::callIt('Foo::someAwesomeMethod');
?>

我认为这是因为 FunctionCaller 无法知道字符串 'self' 在某个时候指的是 `Foo`。
175
metamarkers at gmail dot com
11 年前
如果它的类定义了 __invoke() 魔术方法,您可以将对象作为可调用对象传递。
113
mariano dot REMOVE dot perez dot rodriguez at gmail dot com
8 年前
我需要一个函数来确定传递的可调用对象的类型,并最终
将其标准化。以下是我想出的方法

<?php

/**
* 下表列出了可调用类型和规范化结果:
*
* 可调用 | 规范化 | 类型
* ---------------------------------+---------------------------------+--------------
* 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'
* ---------------------------------+---------------------------------+--------------
* 其他可调用 | 同上 | 'unknown'
* ---------------------------------+---------------------------------+--------------
* 非可调用 | null | false
*
* 如果 "strict" 参数设置为 true,则会执行额外的检查,特别是:
* - 当给出 "class::method" 格式的可调用字符串或 ["class", "method"] 格式的可调用数组时,该方法必须是静态方法。
* - 当给出 [$object, "method"] 格式的可调用数组时,该方法必须是非静态方法。
*
*/
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;
}

?>

希望其他人也能发现它的用处。
9
InvisibleSmiley
3 年前
如果你将一个可调用方法传递给一个带有可调用类型声明的函数,错误信息会误导人。

<?php
class X {
protected function
foo(): void {}
}

function
bar(callable $c) {}

$x = new X;
$c = [$x, 'foo'];
bar($c);
?>

错误信息可能是 "参数 #1 ($c) 必须是可调用类型,但给定的是数组",而实际上问题只在于 "foo" 方法的可见性。你只需要将其改为 public(或者使用其他方法,例如使用闭包)。
23
bradyn at NOSPAM dot bradynpoulsen dot com
8 年前
当尝试从命名空间中的函数名创建可调用对象时,你必须提供函数的完全限定名(无论当前命名空间或 use 语句是什么)。

<?php

namespace MyNamespace;

function
doSomethingFancy($arg1)
{
// 做一些事情...
}

$values = [1, 2, 3];

array_map('doSomethingFancy', $values);
// array_map() 期待参数 1 是一个有效的回调,但没有找到函数 'doSomethingFancy' 或函数名无效

array_map('MyNamespace\doSomethingFancy', $values);
// => [..., ..., ...]
8
gulaschsuppe2 at gmail dot com
5 年前
我在 3v4l 上尝试了许多直接调用函数并将其分配给变量的方法。目前尚未提及的是,至少从 PHP 7.1.25 开始,可以使用数组作为调用者。以下脚本包含我获得的所有信息。

<?php

// 通过函数名调用函数:
// 基础知识:
// 函数也可以通过使用其字符串名称来调用:
function callbackFunc() {
echo
'Hello World';
}

'callbackFunc'(); // Hello World

// 如果函数名被分配给一个变量,也可以调用它:
function callbackFunc() {
echo
'Hello World';
}

$funcName = 'callbackFunc';
$funcName(); // Hello World

// 静态类方法:
// 也可以通过 'ClassName::functioName' 符号调用公共静态类方法:
class A {
public static function
callbackMethod() {
echo
"Hello World\n";
}
}
'A::callbackMethod'(); // Hello World

$funcName = 'A::callbackMethod';
$funcName(); // Hello World

// 非静态类方法:
// 也可以通过创建一个数组来调用非静态类方法,该数组的第一项是应该调用该方法的对象,第二项是将要调用的非静态方法。该数组可以直接用作调用者:
class A {
private
$prop = "Hello World\n";

public function
callbackMethod() {
echo
$this->prop;
}
}

$a = new A;
[
$a, 'callbackMethod']();
$funcCallArr = [$a, 'callbackMethod'];
$funcCallArr();

// 当然,这在类内部使用 '$this' 也可以:
class A {
private function
privCallback() {
echo
'Private';
}

public function
privCallbackCaller($funcName) {
[
$this, $funcName]();
}
}

(new
A)->privCallbackCaller('privCallback'); // Private

?>
3
pawel dot tadeusz dot niedzielski at gmail dot com
8 年前
@edanschwartz at gmail dot com

可以使用 ::class 属性来始终指示使用静态方法时所在的类

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

public static function
someAwesomeMethod() {
// 这里有很棒的代码。
}
}

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

Foo::doAwesomeThings();
?>
1
chris dot rutledge at gmail dot com
5 年前
阅读了上面手册中的这行代码后,

"实例化对象的方法被传递为一个数组,该数组包含索引 0 处的对象和索引 1 处的 方法名。允许从类内部访问受保护和私有方法。"

我决定做一些测试,看看是否可以使用 call_user_func 方法访问私有方法。值得庆幸的是不行,但为了完整性,这里是我的测试,它还涵盖了使用静态和对象上下文

<?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']); //false, foo:bar

call_user_func(['foo','baz']); //warning, 无法访问私有方法

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

call_user_func(['foo','qux']); //fatal, 在非对象上下文中使用 $this

$foo = new foo;

call_user_func([$foo,'bar']); //true, foo::bar
call_user_func([$foo,'baz']); //warning, 无法访问私有方法
call_user_func([$foo,'qux']); //true, foo::baz

call_user_func(['foo','bar']); //true, foo::bar (静态调用,但 $isInstance 为 true)

?>
To Top