mirror of
https://github.com/php/doc-zh.git
synced 2026-03-23 22:52:08 +01:00
1638 lines
43 KiB
XML
1638 lines
43 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
||
<!-- $Revision$ -->
|
||
<!-- EN-Revision: dd87866772c31671146ff778140dc0955c55005c Maintainer: avenger Status: ready -->
|
||
<!-- CREDITS: Gregory, dallas, Luffy, mowangjuanzi -->
|
||
<chapter xml:id="language.functions" xmlns="http://docbook.org/ns/docbook">
|
||
<title>函数</title>
|
||
|
||
<sect1 xml:id="functions.user-defined">
|
||
<title>用户自定义函数</title>
|
||
|
||
<para>
|
||
函数的定义是通过使用 <literal>function</literal>
|
||
关键字、名称、由逗号(<literal>,</literal>)分隔并用圆括号括起来的参数列表(可能为空),后接包含使用大括号括起来的函数主体,例如以下形式:
|
||
</para>
|
||
<example>
|
||
<title>声明名为 <literal>foo</literal> 的新函数</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
function foo($arg_1, $arg_2, /* ..., */ $arg_n)
|
||
{
|
||
echo "Example function.\n";
|
||
return $retval;
|
||
}
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
<note>
|
||
<para>
|
||
自 PHP 8.0.0 起,参数列表可以包含尾随逗号:
|
||
<informalexample>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
function foo($arg_1, $arg_2,) { }
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</informalexample>
|
||
</para>
|
||
</note>
|
||
|
||
<simpara>
|
||
任何有效的 PHP 代码都有可能出现在函数主体,甚至包括其它函数和 <link linkend="language.oop5.basic.class">class</link> 定义。
|
||
</simpara>
|
||
<para>
|
||
函数名和 PHP 中的其它标识符命名规则相同。有效的函数名以字母或下划线打头,后面跟字母,数字或下划线。可以用正则表达式表示为
|
||
<code>^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$</code>。
|
||
</para>
|
||
&tip.userlandnaming;
|
||
<simpara>
|
||
函数无需在调用之前被定义,<emphasis>除非</emphasis>是下面两个例子中函数是有条件被定义时。
|
||
</simpara>
|
||
<para>
|
||
当一个函数是有条件被定义时,必须在调用函数<emphasis>之前</emphasis>定义。
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>有条件的函数</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
$makefoo = true;
|
||
|
||
/* 不能在此处调用foo()函数,
|
||
因为它还不存在,但可以调用bar()函数。*/
|
||
|
||
bar();
|
||
|
||
if ($makefoo) {
|
||
function foo()
|
||
{
|
||
echo "I don't exist until program execution reaches me.\n";
|
||
}
|
||
}
|
||
|
||
/* 现在可以安全调用函数 foo()
|
||
因为 $makefoo 值为真 */
|
||
|
||
if ($makefoo) foo();
|
||
|
||
function bar()
|
||
{
|
||
echo "I exist immediately upon program start.\n";
|
||
}
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>函数中的函数</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
function foo()
|
||
{
|
||
function bar()
|
||
{
|
||
echo "I don't exist until foo() is called.\n";
|
||
}
|
||
}
|
||
|
||
/* 现在还不能调用 bar() 函数,因为它还不存在 */
|
||
|
||
foo();
|
||
|
||
/* 现在可以调用 bar() 函数了,因为 foo() 函数
|
||
的执行使得 bar() 函数变为已定义的函数 */
|
||
|
||
bar();
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
PHP 中的所有函数和类都具有全局作用域,可以定义在一个函数之内而在之外调用,反之亦然。
|
||
</para>
|
||
<simpara>
|
||
PHP 不支持函数重载,也不可能取消定义或者重定义已声明的函数。
|
||
</simpara>
|
||
<note>
|
||
<simpara>
|
||
从 <literal>A</literal> 到 <literal>Z</literal>
|
||
的 ASCII 函数名是大小写无关的,不过在调用函数的时候,使用其在定义时相同的形式是个好习惯。
|
||
</simpara>
|
||
</note>
|
||
<simpara>
|
||
函数支持 <link linkend="functions.variable-arg-list">可变数量的参数</link> 和
|
||
<link linkend="functions.arguments.default">默认参数</link>。参见
|
||
<function>func_num_args</function>、<function>func_get_arg</function> 和
|
||
<function>func_get_args</function> 的函数参考以获取更多信息。
|
||
</simpara>
|
||
|
||
<para>
|
||
在 PHP 中可以调用递归函数。
|
||
<example>
|
||
<title>递归函数</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
function recursion($a)
|
||
{
|
||
if ($a < 20) {
|
||
echo "$a\n";
|
||
recursion($a + 1);
|
||
}
|
||
}
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
<note>
|
||
<simpara>
|
||
但是要避免递归函数/方法调用超过 100-200 层,因为可能会使堆栈崩溃从而使当前脚本终止。
|
||
无限递归可视为编程错误。
|
||
</simpara>
|
||
</note>
|
||
</para>
|
||
|
||
</sect1>
|
||
|
||
<sect1 xml:id="functions.arguments">
|
||
<title>函数的参数和参数值</title>
|
||
|
||
<simpara>
|
||
函数参数在函数签名中声明。通过参数值列表可以传递信息到函数,即以逗号作为分隔符的表达式列表。函数在实际调用之前,值参数是从左向右求值的(<emphasis>及早</emphasis>求值),并将结果赋值给函数的参数。
|
||
</simpara>
|
||
|
||
<para>
|
||
PHP 支持按值传递参数值(默认),<link
|
||
linkend="functions.arguments.by-reference">通过引用传递参数</link>以及<link
|
||
linkend="functions.arguments.default">默认参数</link>。也支持<link
|
||
linkend="functions.variable-arg-list">可变长度参数列表</link>和<link
|
||
linkend="functions.named-arguments">命名参数</link>。
|
||
</para>
|
||
<note>
|
||
<para>
|
||
自 PHP 7.3.0 起,函数调用的参数列表中可以包含尾随逗号:
|
||
<informalexample>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
$v = foo(
|
||
$arg_1,
|
||
$arg_2,
|
||
);
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</informalexample>
|
||
</para>
|
||
</note>
|
||
|
||
<para>
|
||
自 PHP 8.0.0 起,函数参数列表可以包含一个尾部的逗号,这个逗号将被忽略。这在参数列表较长或包含较长的变量名的情况下特别有用,这样可以方便地垂直列出参数。
|
||
</para>
|
||
<example>
|
||
<title>函数参数使用尾部逗号</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
function takes_many_args(
|
||
$first_arg,
|
||
$second_arg,
|
||
$a_very_long_argument_name,
|
||
$arg_with_default = 5,
|
||
$again = 'a default string', // 在 8.0.0 之前,这个尾部的逗号是不允许的。
|
||
)
|
||
{
|
||
// ...
|
||
}
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
|
||
<sect2 xml:id="functions.arguments.by-reference">
|
||
<title>通过引用传递参数值</title>
|
||
|
||
<simpara>
|
||
默认情况下,函数参数值通过值传递(因而即使在函数内部改变参数值,它并不会改变函数外部的值)。如果希望允许函数修改它的参数值,必须通过引用传递。
|
||
</simpara>
|
||
<para>
|
||
如果想要函数的参数值始终通过引用传递,可以在函数定义中该参数的前面加上符号 &:
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>用引用传递函数参数值</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
function add_some_extra(&$string)
|
||
{
|
||
$string .= 'and something extra.';
|
||
}
|
||
$str = 'This is a string, ';
|
||
add_some_extra($str);
|
||
echo $str; // 输出“This is a string, and something extra.”
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
将常量表达式作为参数值传递给需要通过引用传递的参数是错误的。
|
||
</para>
|
||
</sect2>
|
||
<sect2 xml:id="functions.arguments.default">
|
||
<title>默认参数的值</title>
|
||
|
||
<para>
|
||
函数可以使用类似分配变量的语法定义参数的默认值。仅当参数未传递值时才使用默认值;注意传递
|
||
&null; <emphasis>不会</emphasis>分配默认值。
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>在函数中使用默认参数</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
function makecoffee($type = "cappuccino")
|
||
{
|
||
return "Making a cup of $type.\n";
|
||
}
|
||
echo makecoffee();
|
||
echo makecoffee(null);
|
||
echo makecoffee("espresso");
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
Making a cup of cappuccino.
|
||
Making a cup of .
|
||
Making a cup of espresso.
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
默认参数值可以是标量值、<type>array</type>、特殊类型 &null;,以及从 PHP 8.1.0 开始,使用
|
||
<link linkend="language.oop5.basic.new">new ClassName()</link> 语法的对象。
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>使用非标量类型作为默认参数</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
function makecoffee($types = array("cappuccino"), $coffeeMaker = NULL)
|
||
{
|
||
$device = is_null($coffeeMaker) ? "hands" : $coffeeMaker;
|
||
return "Making a cup of ".join(", ", $types)." with $device.\n";
|
||
}
|
||
echo makecoffee();
|
||
echo makecoffee(array("cappuccino", "lavazza"), "teapot");?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
Making a cup of cappuccino with hands.
|
||
Making a cup of cappuccino, lavazza with teapot.
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>使用对象作为默认值(自 PHP 8.1.0 起)</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
class DefaultCoffeeMaker {
|
||
public function brew() {
|
||
return "Making coffee.\n";
|
||
}
|
||
}
|
||
class FancyCoffeeMaker {
|
||
public function brew() {
|
||
return "Crafting a beautiful coffee just for you.\n";
|
||
}
|
||
}
|
||
function makecoffee($coffeeMaker = new DefaultCoffeeMaker)
|
||
{
|
||
return $coffeeMaker->brew();
|
||
}
|
||
echo makecoffee();
|
||
echo makecoffee(new FancyCoffeeMaker);
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
Making coffee.
|
||
Crafting a beautiful coffee just for you.
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</para>
|
||
<simpara>
|
||
默认值必须是常量表达式,不能是诸如变量,类成员,或者函数调用等。
|
||
</simpara>
|
||
<para>
|
||
注意任何可选参数都应在强制参数之后指定,否则可选参数不能在调用时省略。考虑以下示例:
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>函数默认参数的不正确用法</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
function makeyogurt($container = "bowl", $flavour)
|
||
{
|
||
return "Making a $container of $flavour yogurt.\n";
|
||
}
|
||
|
||
echo makeyogurt("raspberry"); // "raspberry" 是 $container, 不是 $flavour
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
Fatal error: Uncaught ArgumentCountError: Too few arguments
|
||
to function makeyogurt(), 1 passed in example.php on line 42
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
现在,比较上面的例子和这个例子:
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>函数默认参数正确的用法</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
function makeyogurt($flavour, $container = "bowl")
|
||
{
|
||
return "Making a $container of $flavour yogurt.\n";
|
||
}
|
||
|
||
echo makeyogurt("raspberry"); // "raspberry" 是 $flavour
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
Making a bowl of raspberry yogurt.
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
自 PHP 8.0.0 起,<link linkend="functions.named-arguments">命名参数</link>可用于跳过多个可选参数。
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>函数默认参数正确的用法</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
function makeyogurt($container = "bowl", $flavour = "raspberry", $style = "Greek")
|
||
{
|
||
return "Making a $container of $flavour $style yogurt.\n";
|
||
}
|
||
|
||
echo makeyogurt(style: "natural");
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
Making a bowl of raspberry natural yogurt.
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
自 PHP 8.0.0 起,<emphasis>弃用</emphasis>在可选参数之后声明强制参数。这通常可以通过删除默认值来解决,因为它永远不会被使用。唯一的例外是
|
||
<code>Type $param = null</code> 类型的参数,其中默认 &null; 使得该类型可以隐式为 null。自 PHP 8.4.0
|
||
起,此用法已弃用,而应改用显式<link linkend="language.types.declarations.nullable">可为 null 类型</link>。
|
||
<example>
|
||
<title>强制参数后声明可选参数</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
function foo($a = [], $b) {} // 默认不使用;自 PHP 8.0.0 起弃用
|
||
function foo($a, $b) {} // 功能相同,无弃用通知
|
||
|
||
function bar(A $a = null, $b) {} // 自 PHP 8.1.0 起,$a 是隐式必需的
|
||
// (因为其位于必需的参数之前),
|
||
// 但隐式可为 null(自 PHP 8.4.0 起已弃用),
|
||
// 因为默认参数值为 null
|
||
function bar(?A $a, $b) {} // 推荐
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<note>
|
||
<simpara>
|
||
自 PHP 7.1.0 起,省略未指定默认值的参数会原因引发
|
||
<classname>ArgumentCountError</classname>;在此之前的版本会引发警告。
|
||
</simpara>
|
||
</note>
|
||
<note>
|
||
<simpara>
|
||
期望通过引用传参的参数可以有默认值。
|
||
</simpara>
|
||
</note>
|
||
</sect2>
|
||
|
||
<sect2 xml:id="functions.variable-arg-list">
|
||
<title>可变数量的参数值列表</title>
|
||
|
||
<simpara>
|
||
PHP 在用户自定义函数中支持可变数量的参数值列表。由
|
||
<literal>...</literal> 语法实现。
|
||
</simpara>
|
||
|
||
<para>
|
||
参数列表可能包含 <literal>...</literal> 记号,表示该函数接受可变数量的参数值。参数值将作为
|
||
&array; 传递到指定变量中:
|
||
|
||
<example>
|
||
<title>使用 <literal>...</literal> 来访问变量参数值</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
function sum(...$numbers) {
|
||
$acc = 0;
|
||
foreach ($numbers as $n) {
|
||
$acc += $n;
|
||
}
|
||
return $acc;
|
||
}
|
||
|
||
echo sum(1, 2, 3, 4);
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
10
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</para>
|
||
|
||
<para>
|
||
<literal>...</literal> 也可以在调用函数时将 <type>array</type> 或 <classname>Traversable</classname> 变量或文字解包到参数值列表中使用:
|
||
|
||
<example>
|
||
<title>使用 <literal>...</literal> 来传递参数</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
function add($a, $b) {
|
||
return $a + $b;
|
||
}
|
||
|
||
echo add(...[1, 2])."\n";
|
||
|
||
$a = [1, 2];
|
||
echo add(...$a);
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
3
|
||
3
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</para>
|
||
|
||
<para>
|
||
可以在 <literal>...</literal>
|
||
前指定正常的位置参数。在这种情况下,只有不符合位置参数的尾部参数才会被添加到
|
||
<literal>...</literal> 生成的数组中。
|
||
</para>
|
||
|
||
<para>
|
||
也可以在 <literal>...</literal> 标记前添加 <link
|
||
linkend="language.types.declarations">类型声明</link>。如果存在这种情况,那么
|
||
<literal>...</literal> 捕获的所有参数都必须匹配参数类型。
|
||
|
||
<example>
|
||
<title>输入提示的变量参数</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
function total_intervals($unit, DateInterval ...$intervals) {
|
||
$time = 0;
|
||
foreach ($intervals as $interval) {
|
||
$time += $interval->$unit;
|
||
}
|
||
return $time;
|
||
}
|
||
|
||
$a = new DateInterval('P1D');
|
||
$b = new DateInterval('P2D');
|
||
echo total_intervals('d', $a, $b).' days';
|
||
|
||
// 这将会失败,因为 null 不是 DateInterval 对象。
|
||
echo total_intervals('d', null);
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
3 days
|
||
Catchable fatal error: Argument 2 passed to total_intervals() must be an instance of DateInterval, null given, called in - on line 14 and defined in - on line 2
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</para>
|
||
|
||
<para>
|
||
最后,可变参数值也可以通过在 <literal>...</literal> 前面加上 <literal>&</literal>
|
||
来通过<link linkend="functions.arguments.by-reference">引用</link>传递。
|
||
</para>
|
||
|
||
</sect2>
|
||
|
||
<sect2 xml:id="functions.named-arguments">
|
||
<title>命名参数</title>
|
||
|
||
<para>
|
||
PHP 8.0.0 开始引入了命名参数作为现有位置参数的扩展。命名参数允许根据参数名而不是参数位置向函数传参。这使得参数的含义自成体系,参数与顺序无关,并允许任意跳过默认值。
|
||
</para>
|
||
|
||
<para>
|
||
命名参数通过在参数名前加上冒号来传递。允许使用保留关键字作为参数名。参数名必须是一个标识符,不允许动态指定。
|
||
</para>
|
||
|
||
<example>
|
||
<title>命名参数的语法</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
myFunction(paramName: $value);
|
||
array_foobar(array: $value);
|
||
|
||
// 不支持。
|
||
function_name($variableStoringParamName: $value);
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
|
||
<example>
|
||
<title>通过位置传参与命名参数的对比</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
// 使用顺序传递参数:
|
||
array_fill(0, 100, 50);
|
||
|
||
// 使用命名参数:
|
||
array_fill(start_index: 0, count: 100, value: 50);
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
|
||
<para>
|
||
指定参数的传递顺序并不重要。
|
||
</para>
|
||
|
||
<example>
|
||
<title>参数顺序不同的示例(同上例)</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
array_fill(value: 50, count: 100, start_index: 0);
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
|
||
<para>
|
||
命名参数也可以与位置参数相结合使用。此种情况下,命名参数必须在位置参数之后。也可以只指定一个函数的部分可选参数,而不考虑它们的顺序。
|
||
</para>
|
||
|
||
<example>
|
||
<title>命名参数与位置参数结合使用</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
htmlspecialchars($string, double_encode: false);
|
||
// 等价于
|
||
htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8', false);
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
|
||
<para>
|
||
传递多个参数值给同一个命名参数会导致 <classname>Error</classname> 异常。
|
||
</para>
|
||
|
||
<example>
|
||
<title>传递多个参数值到相同命名参数将会导致抛出 Error</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
function foo($param) { ... }
|
||
|
||
foo(param: 1, param: 2);
|
||
// 错误:命名参数 $param 覆盖了之前的参数
|
||
|
||
foo(1, param: 2);
|
||
// 错误:命名参数 $param 覆盖了之前的参数
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
|
||
<para>
|
||
自 PHP 8.1.0 起,可以在解包参数后面使用命名参数。命名参数<emphasis>不能</emphasis>覆盖已解包的参数。
|
||
</para>
|
||
|
||
<example>
|
||
<title>解包后使用命名参数</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
function foo($a, $b, $c = 3, $d = 4) {
|
||
return $a + $b + $c + $d;
|
||
}
|
||
|
||
var_dump(foo(...[1, 2], d: 40)); // 46
|
||
var_dump(foo(...['b' => 2, 'a' => 1], d: 40)); // 46
|
||
|
||
var_dump(foo(...[1, 2], b: 20)); // Fatal error。命名参数 $b 覆盖之前的参数
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
|
||
</sect2>
|
||
</sect1>
|
||
|
||
<sect1 xml:id="functions.returning-values">
|
||
<title>返回值</title>
|
||
|
||
<para>
|
||
值通过使用可选的返回语句返回。可以返回包括数组和对象的任意类型。返回语句会立即中止函数的运行,并且将控制权交回调用该函数的代码行。更多信息见
|
||
<function>return</function>。
|
||
</para>
|
||
|
||
<note>
|
||
<para>
|
||
如果省略了
|
||
<function>return</function>,则返回值为 &null;。
|
||
</para>
|
||
</note>
|
||
|
||
<sect2>
|
||
<title>return 的使用</title>
|
||
<para>
|
||
<example>
|
||
<title><function>return</function> 的使用</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
function square($num)
|
||
{
|
||
return $num * $num;
|
||
}
|
||
echo square(4); // 输出 '16'。
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
|
||
<para>
|
||
函数不能返回多个值,但可以通过返回一个数组来得到类似的效果。
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>返回一个数组以得到多个返回值</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
function small_numbers()
|
||
{
|
||
return [0, 1, 2];
|
||
}
|
||
// 使用短数组语法将数组中的值赋给一组变量
|
||
[$zero, $one, $two] = small_numbers();
|
||
|
||
// 在 7.1.0 之前,唯一相等的选择是使用 list() 结构
|
||
list($zero, $one, $two) = small_numbers();
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
从函数返回一个引用,必须在函数声明和指派返回值给一个变量时都使用引用运算符 &:
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>从函数返回一个引用</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
function &returns_reference()
|
||
{
|
||
return $someref;
|
||
}
|
||
|
||
$newref =& returns_reference();
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<simpara>
|
||
有关引用的更多信息, 请查看 <link
|
||
linkend="language.references">引用的解释</link>。
|
||
</simpara>
|
||
</sect2>
|
||
</sect1>
|
||
|
||
<sect1 xml:id="functions.variable-functions">
|
||
<title>可变函数</title>
|
||
|
||
<para>
|
||
PHP 支持可变函数的概念。这意味着如果一个变量名后有圆括号,PHP
|
||
将寻找与变量的值同名的函数,并且尝试执行它。可变函数可以用来实现包括回调函数,函数表在内的一些用途。
|
||
</para>
|
||
<para>
|
||
可变函数不能用于例如
|
||
<function>echo</function>,<function>print</function>,<function>unset</function>,<function>isset</function>,<function>empty</function>,<function>include</function>,<function>require</function>
|
||
以及类似的语言结构。需要使用自己的包装函数来将这些结构用作可变函数。
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>可变函数示例</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
function foo() {
|
||
echo "In foo()<br />\n";
|
||
}
|
||
|
||
function bar($arg = '')
|
||
{
|
||
echo "In bar(); argument was '$arg'.<br />\n";
|
||
}
|
||
|
||
// 使用 echo 的包装函数
|
||
function echoit($string)
|
||
{
|
||
echo $string;
|
||
}
|
||
|
||
$func = 'foo';
|
||
$func(); // 调用 foo()
|
||
|
||
$func = 'bar';
|
||
$func('test'); // 调用 bar()
|
||
|
||
$func = 'echoit';
|
||
$func('test'); // 调用 echoit()
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
也可以用可变函数的语法来调用一个对象的方法。
|
||
<example>
|
||
<title>可变方法范例</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
class Foo
|
||
{
|
||
function Variable()
|
||
{
|
||
$name = 'Bar';
|
||
$this->$name(); // 调用 Bar() 方法
|
||
}
|
||
|
||
function Bar()
|
||
{
|
||
echo "This is Bar";
|
||
}
|
||
}
|
||
|
||
$foo = new Foo();
|
||
$funcname = "Variable";
|
||
$foo->$funcname(); // 调用 $foo->Variable()
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
当调用静态方法时,函数调用要比静态属性优先:
|
||
<example>
|
||
<title>Variable 方法和静态属性示例</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
class Foo
|
||
{
|
||
static $variable = 'static property';
|
||
static function Variable()
|
||
{
|
||
echo 'Method Variable called';
|
||
}
|
||
}
|
||
|
||
echo Foo::$variable; // 打印 'static property'。在该作用域中需要 $variable。
|
||
$variable = "Variable";
|
||
Foo::$variable(); // 在该作用域中读取 $variable 调用 $foo->Variable()。
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>复杂调用</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
class Foo
|
||
{
|
||
static function bar()
|
||
{
|
||
echo "bar\n";
|
||
}
|
||
function baz()
|
||
{
|
||
echo "baz\n";
|
||
}
|
||
}
|
||
|
||
$func = array("Foo", "bar");
|
||
$func(); // 打印 "bar"
|
||
$func = array(new Foo, "baz");
|
||
$func(); // 打印 "baz"
|
||
$func = "Foo::bar";
|
||
$func(); // 打印 "bar"
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
|
||
<sect2 role="seealso">
|
||
&reftitle.seealso;
|
||
<para>
|
||
<simplelist>
|
||
<member><function>is_callable</function></member>
|
||
<member><function>call_user_func</function></member>
|
||
<member><function>function_exists</function></member>
|
||
<member><link linkend="language.variables.variable">可变变量</link></member>
|
||
</simplelist>
|
||
</para>
|
||
</sect2>
|
||
</sect1>
|
||
|
||
<sect1 xml:id="functions.internal">
|
||
<title>内部(内置)函数</title>
|
||
|
||
<para>
|
||
PHP 有很多标准的函数和结构。还有一些函数需要和特定地 PHP
|
||
扩展模块一起编译,否则在使用它们的时候就会得到一个致命的“未定义函数”错误。例如,要使用
|
||
<link linkend="ref.image">image</link> 函数中的
|
||
<function>imagecreatetruecolor</function>,需要在编译 PHP 的时候加上
|
||
<productname>GD</productname> 的支持。或者,要使用
|
||
<function>mysqli_connect</function> 函数,就需要在编译 PHP 的时候加上
|
||
<link linkend="book.mysqli">MySQLi</link> 支持。有很多核心函数已包含在每个版本的
|
||
PHP 中如<link linkend="ref.strings">字符串</link>和<link
|
||
linkend="ref.var">变量</link>函数。调用
|
||
<function>phpinfo</function> 或者 <function>get_loaded_extensions</function>
|
||
可以得知 PHP 加载了那些扩展库。同时还应该注意,很多扩展库默认就是有效的。PHP
|
||
手册按照不同的扩展库组织了它们的文档。请参阅<link linkend="configuration">配置</link>,<link
|
||
linkend="install">安装</link>以及各自的扩展库章节以获取有关如何设置 PHP 的信息。
|
||
</para>
|
||
<para>
|
||
手册中<link
|
||
linkend="about.prototypes">如何阅读函数原型</link>讲解了如何阅读和理解一个函数的原型。确认一个函数将返回什么,或者函数是否直接作用于传递的参数是很重要的。例如,<function>str_replace</function>
|
||
函数将返回修改过的字符串,而 <function>usort</function>
|
||
却直接作用于传递的参数变量本身。手册中,每一个函数的页面中都有关于函数参数、行为改变、成功与否的返回值以及使用条件等信息。了解这些重要的(常常是细微的)差别是编写正确的
|
||
PHP 代码的关键。
|
||
</para>
|
||
<note>
|
||
<simpara>
|
||
如果传递给函数的参数类型与实际的类型不一致,例如将一个 <type>array</type>
|
||
传递给一个 <type>string</type> 类型的变量,那么函数的返回值是不确定的。在这种情况下,通常函数会返回
|
||
&null;。但这仅仅是一个惯例,并不一定如此。从 PHP 8.0.0 起,这种情况下应抛出 <classname>TypeError</classname> 异常。
|
||
</simpara>
|
||
</note>
|
||
<note>
|
||
<para>
|
||
在强制模式下内置函数的标量类型默认可以为 null。自 PHP 8.1.0 起,弃用传递 &null; 到声明为非 null
|
||
的内部函数参数,并在强制类型下发出弃用通知,与用户定义函数的行为保持一致,其中标量类型需要显式标记为可以为 null。
|
||
</para>
|
||
|
||
<para>
|
||
例如,<function>strlen</function> 函数接受参数 <literal>$string</literal> 为非 null 的
|
||
&string;。由于历史原因,在强制模式下 PHP 允许此参数接受 &null;,并将参数隐式转化为
|
||
<type>string</type>,从而产生 <literal>""</literal> 值。相比之下,严格模式会抛出 <classname>TypeError</classname>。
|
||
</para>
|
||
|
||
<informalexample>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
var_dump(strlen(null));
|
||
// "Deprecated: Passing null to parameter #1 ($string) of type string is deprecated" as of PHP 8.1.0
|
||
// int(0)
|
||
|
||
var_dump(str_contains("foobar", null));
|
||
// "Deprecated: Passing null to parameter #2 ($needle) of type string is deprecated" as of PHP 8.1.0
|
||
// bool(true)
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</informalexample>
|
||
</note>
|
||
|
||
<sect2 role="seealso">
|
||
&reftitle.seealso;
|
||
<para>
|
||
<simplelist>
|
||
<member><function>function_exists</function></member>
|
||
<member><link linkend="funcref">函数参考</link></member>
|
||
<member><function>get_extension_funcs</function></member>
|
||
<member><function>dl</function></member>
|
||
</simplelist>
|
||
</para>
|
||
</sect2>
|
||
</sect1>
|
||
|
||
<sect1 xml:id="functions.anonymous">
|
||
<title>匿名函数</title>
|
||
|
||
<simpara>
|
||
匿名函数(Anonymous functions),也叫闭包函数(<literal>closures</literal>),允许
|
||
临时创建一个没有指定名称的函数。最经常用作回调函数 <type>callable</type>参数的值。当然,也有其它应用的情况。
|
||
</simpara>
|
||
<simpara>
|
||
匿名函数目前是通过 <link linkend="class.closure">
|
||
<classname>Closure</classname></link> 类来实现的。
|
||
</simpara>
|
||
|
||
<example>
|
||
<title>匿名函数示例</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
echo preg_replace_callback('~-([a-z])~', function ($match) {
|
||
return strtoupper($match[1]);
|
||
}, 'hello-world');
|
||
// 输出 helloWorld
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
|
||
<simpara>
|
||
闭包函数也可以作为变量的值来使用。PHP 会自动把此种表达式转换成内置类
|
||
<classname>Closure</classname> 的对象实例。把一个 closure
|
||
对象赋值给一个变量的方式与普通变量赋值的语法是一样的,最后也要加上分号:
|
||
</simpara>
|
||
|
||
<example>
|
||
<title>匿名函数变量赋值示例</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
$greet = function($name) {
|
||
printf("Hello %s\r\n", $name);
|
||
};
|
||
|
||
$greet('World');
|
||
$greet('PHP');
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
|
||
<simpara>
|
||
闭包可以从父作用域中继承变量。
|
||
任何此类变量都应该用 <literal>use</literal> 语言结构传递进去。
|
||
PHP 7.1 起,不能传入此类变量: &link.superglobals;、 <varname>$this</varname> 或者和参数重名。
|
||
返回类型声明必须放在 <literal>use</literal> 子句的 <emphasis>后面</emphasis> 。
|
||
</simpara>
|
||
|
||
<example>
|
||
<title>从父作用域继承变量</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
$message = 'hello';
|
||
|
||
// 没有 "use"
|
||
$example = function () {
|
||
var_dump($message);
|
||
};
|
||
$example();
|
||
|
||
// 继承 $message
|
||
$example = function () use ($message) {
|
||
var_dump($message);
|
||
};
|
||
$example();
|
||
|
||
// 当函数被定义而不是被调用的时候继承变量的值
|
||
$message = 'world';
|
||
$example();
|
||
|
||
// 重置 message
|
||
$message = 'hello';
|
||
|
||
// 通过引用继承
|
||
$example = function () use (&$message) {
|
||
var_dump($message);
|
||
};
|
||
$example();
|
||
|
||
// 父级作用域改变的值反映在函数调用中
|
||
$message = 'world';
|
||
$example();
|
||
|
||
// 闭包函数也可以接受常规参数
|
||
$example = function ($arg) use ($message) {
|
||
var_dump($arg . ' ' . $message);
|
||
};
|
||
$example("hello");
|
||
|
||
// 返回类型在 use 子句的后面
|
||
$example = function () use ($message): string {
|
||
return "hello $message";
|
||
};
|
||
var_dump($example());
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs.similar;
|
||
<screen>
|
||
<![CDATA[
|
||
Notice: Undefined variable: message in /example.php on line 6
|
||
NULL
|
||
string(5) "hello"
|
||
string(5) "hello"
|
||
string(5) "hello"
|
||
string(5) "world"
|
||
string(11) "hello world"
|
||
string(11) "hello world"
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
|
||
<para>
|
||
从 PHP 8.0.0 开始,作用域继承的变量列表可能包含一个尾部的逗号,这个逗号将被忽略。
|
||
</para>
|
||
<simpara>
|
||
这些变量都必须在函数或类的头部声明。
|
||
|
||
从父作用域中继承变量与使用全局变量是<emphasis>不同</emphasis>的。全局变量存在于一个全局的范围,无论当前在执行的是哪个函数。而
|
||
闭包的父作用域是定义该闭包的函数(不一定是调用它的函数)。示例如下:
|
||
</simpara>
|
||
|
||
<example>
|
||
<title>Closures 和作用域</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
// 一个基本的购物车,包括一些已经添加的商品和每种商品的数量。
|
||
// 其中有一个方法用来计算购物车中所有商品的总价格,该方法使
|
||
// 用了一个 closure 作为回调函数。
|
||
class Cart
|
||
{
|
||
const PRICE_BUTTER = 1.00;
|
||
const PRICE_MILK = 3.00;
|
||
const PRICE_EGGS = 6.95;
|
||
|
||
protected $products = array();
|
||
|
||
public function add($product, $quantity)
|
||
{
|
||
$this->products[$product] = $quantity;
|
||
}
|
||
|
||
public function getQuantity($product)
|
||
{
|
||
return isset($this->products[$product]) ? $this->products[$product] :
|
||
FALSE;
|
||
}
|
||
|
||
public function getTotal($tax)
|
||
{
|
||
$total = 0.00;
|
||
|
||
$callback =
|
||
function ($quantity, $product) use ($tax, &$total)
|
||
{
|
||
$pricePerItem = constant(__CLASS__ . "::PRICE_" .
|
||
strtoupper($product));
|
||
$total += ($pricePerItem * $quantity) * ($tax + 1.0);
|
||
};
|
||
|
||
array_walk($this->products, $callback);
|
||
return round($total, 2);
|
||
}
|
||
}
|
||
|
||
$my_cart = new Cart;
|
||
|
||
// 往购物车里添加条目
|
||
$my_cart->add('butter', 1);
|
||
$my_cart->add('milk', 3);
|
||
$my_cart->add('eggs', 6);
|
||
|
||
// 打出出总价格,其中有 5% 的销售税.
|
||
print $my_cart->getTotal(0.05) . "\n";
|
||
// 最后结果是 54.29
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
|
||
<example>
|
||
<title>自动绑定 <literal>$this</literal></title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
class Test
|
||
{
|
||
public function testing()
|
||
{
|
||
return function() {
|
||
var_dump($this);
|
||
};
|
||
}
|
||
}
|
||
|
||
$object = new Test;
|
||
$function = $object->testing();
|
||
$function();
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
object(Test)#1 (0) {
|
||
}
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
|
||
<para>
|
||
当在类的上下文中声明时,当前的类会自动与之绑定,使得
|
||
<literal>$this</literal> 在函数的作用域中可用。如果不需要当前类的自动绑定,可以使用
|
||
<link linkend="functions.anonymous-functions.static">静态匿名函数</link> 替代。
|
||
</para>
|
||
|
||
<sect2 xml:id="functions.anonymous-functions.static">
|
||
<title>静态匿名函数</title>
|
||
<para>
|
||
匿名函数允许被定义为静态化。这样可以防止当前类自动绑定到它们身上,对象在运行时也可能不会被绑定到它们上面。
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>试图在静态匿名函数中使用 <literal>$this</literal></title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
class Foo
|
||
{
|
||
function __construct()
|
||
{
|
||
$func = static function() {
|
||
var_dump($this);
|
||
};
|
||
$func();
|
||
}
|
||
};
|
||
new Foo();
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
Notice: Undefined variable: this in %s on line %d
|
||
NULL
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</para>
|
||
|
||
<para>
|
||
<example>
|
||
<title>试图将对象绑定到静态匿名函数</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
$func = static function() {
|
||
// function body
|
||
};
|
||
$func = $func->bindTo(new stdClass);
|
||
$func();
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
Warning: Cannot bind an instance to a static closure in %s on line %d
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</para>
|
||
</sect2>
|
||
|
||
<sect2 role="changelog">
|
||
&reftitle.changelog;
|
||
<para>
|
||
<informaltable>
|
||
<tgroup cols="2">
|
||
<thead>
|
||
<row>
|
||
<entry>&Version;</entry>
|
||
<entry>&Description;</entry>
|
||
</row>
|
||
</thead>
|
||
<tbody>
|
||
<row>
|
||
<entry>8.3.0</entry>
|
||
<entry>
|
||
通过<link linkend="language.oop5.magic">魔术方法</link>创建的闭包可以接受命名参数。
|
||
</entry>
|
||
</row>
|
||
<row>
|
||
<entry>7.1.0</entry>
|
||
<entry>
|
||
匿名函数不能用 use 包含 &link.superglobals; 、<varname>$this</varname> 、
|
||
跟参数同名的变量。
|
||
</entry>
|
||
</row>
|
||
</tbody>
|
||
</tgroup>
|
||
</informaltable>
|
||
</para>
|
||
</sect2>
|
||
|
||
<sect2 role="notes">
|
||
&reftitle.notes;
|
||
<note>
|
||
<simpara>
|
||
可以在闭包中使用
|
||
<function>func_num_args</function>,<function>func_get_arg</function>
|
||
和 <function>func_get_args</function>。
|
||
</simpara>
|
||
</note>
|
||
</sect2>
|
||
|
||
</sect1>
|
||
<sect1 xml:id="functions.arrow">
|
||
<title>箭头函数</title>
|
||
|
||
<simpara>
|
||
箭头函数是 PHP 7.4 的新语法,是一种更简洁的
|
||
<link linkend="functions.anonymous">匿名函数</link> 写法。
|
||
</simpara>
|
||
<simpara>
|
||
匿名函数和箭头函数都是
|
||
<link linkend="class.closure"><classname>Closure</classname></link> 类的实现。
|
||
</simpara>
|
||
|
||
<simpara>
|
||
箭头函数的基本语法为
|
||
<code>fn (argument_list) => expr</code>。
|
||
</simpara>
|
||
|
||
<simpara>
|
||
箭头函数支持与 <link linkend="functions.anonymous">匿名函数</link>
|
||
相同的功能,只是其父作用域的变量总是自动的。
|
||
</simpara>
|
||
|
||
<simpara>
|
||
当表达式中使用的变量是在父作用域中定义的,它将被隐式地按值捕获。在下面的例子中,函数
|
||
<varname>$fn1</varname> 和 <varname>$fn2</varname> 的行为是一样的。
|
||
</simpara>
|
||
|
||
<para>
|
||
<example>
|
||
<title>箭头函数自动捕捉变量的值</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
$y = 1;
|
||
|
||
$fn1 = fn($x) => $x + $y;
|
||
// 相当于通过 value 使用 $y:
|
||
$fn2 = function ($x) use ($y) {
|
||
return $x + $y;
|
||
};
|
||
|
||
var_export($fn1(3));
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
4
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</para>
|
||
<simpara>
|
||
在箭头函数嵌套的情况下同样有效。
|
||
</simpara>
|
||
<para>
|
||
<example>
|
||
<title>箭头函数自动捕捉变量的值,即使在嵌套的情况下</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
$z = 1;
|
||
$fn = fn($x) => fn($y) => $x * $y + $z;
|
||
// 输出 51
|
||
var_export($fn(5)(10));
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<simpara>
|
||
和匿名函数一样,箭头函数语法同样允许标准的函数声明,包括参数和返回类型、缺省值、变量,以及通过引用传递和返回。以下都是箭头函数的有效例子。
|
||
</simpara>
|
||
<para>
|
||
<example>
|
||
<title>合法的箭头函数例子</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
fn(array $x) => $x;
|
||
static fn($x): int => $x;
|
||
fn($x = 42) => $x;
|
||
fn(&$x) => $x;
|
||
fn&($x) => $x;
|
||
fn($x, ...$rest) => $rest;
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<simpara>
|
||
箭头函数会自动绑定上下文变量,这相当于对箭头函数内部使用的每一个变量 <varname>$x</varname> 执行了一个
|
||
<code>use($x)</code>。这意味着不可能修改外部作用域的任何值,若要实现对值的修改,可以使用
|
||
<link linkend="functions.anonymous">匿名函数</link> 来替代。
|
||
</simpara>
|
||
<para>
|
||
<example>
|
||
<title>来自外部范围的值不能在箭头函数内修改</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
$x = 1;
|
||
$fn = fn() => $x++; // 不会影响 x 的值
|
||
$fn();
|
||
var_export($x); // 输出 1
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
|
||
<sect2 role="changelog">
|
||
&reftitle.changelog;
|
||
<para>
|
||
<informaltable>
|
||
<tgroup cols="2">
|
||
<thead>
|
||
<row>
|
||
<entry>&Version;</entry>
|
||
<entry>&Description;</entry>
|
||
</row>
|
||
</thead>
|
||
<tbody>
|
||
<row>
|
||
<entry>7.4.0</entry>
|
||
<entry>
|
||
新增箭头函数语法。
|
||
</entry>
|
||
</row>
|
||
</tbody>
|
||
</tgroup>
|
||
</informaltable>
|
||
</para>
|
||
</sect2>
|
||
|
||
<sect2 role="notes">
|
||
&reftitle.notes;
|
||
<note>
|
||
<simpara>
|
||
可以对箭头函数使用 <function>func_num_args</function>,
|
||
<function>func_get_arg</function>, 和 <function>func_get_args</function> 函数。
|
||
</simpara>
|
||
</note>
|
||
</sect2>
|
||
</sect1>
|
||
|
||
<sect1 xml:id="functions.first_class_callable_syntax">
|
||
<title>一级可调用语法</title>
|
||
|
||
<para>
|
||
从 PHP 8.1.0 开始引入一级可调用语法,作为从 <link linkend="language.types.callable">callable</link> 创建<link
|
||
linkend="functions.anonymous">匿名函数</link>的一种方法。其取代了使用字符串和数组的现有 callable 语法。这种语法的优点是可以进行静态分析,并使用获得可调用对象的作用域。
|
||
</para>
|
||
|
||
<para>
|
||
<code>CallableExpr(...)</code> 语法用于从 callable 创建 <classname>Closure</classname>。<code>CallableExpr</code> 接受任何可以在 PHP 语法中直接调用的表达式:
|
||
<example>
|
||
<title>简单的一级可调用语法</title>
|
||
<programlisting role="php">
|
||
<;
|
||
$f9 = [Foo::class, 'staticmethod'](...);
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
|
||
<note>
|
||
<para>
|
||
<code>...</code> 是语法的一部分,不是遗漏。
|
||
</para>
|
||
</note>
|
||
|
||
<para>
|
||
<code>CallableExpr(...)</code> 与 <methodname>Closure::fromCallable</methodname>
|
||
语义相同。也就是说,与使用字符串和数组的 callable 不同,<code>CallableExpr(...)</code> 尊重其创建时的作用域:
|
||
<example>
|
||
<title><code>CallableExpr(...)</code> 与传统 callable 的作用域比较</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
class Foo {
|
||
public function getPrivateMethod() {
|
||
return [$this, 'privateMethod'];
|
||
}
|
||
|
||
private function privateMethod() {
|
||
echo __METHOD__, "\n";
|
||
}
|
||
}
|
||
|
||
$foo = new Foo;
|
||
$privateMethod = $foo->getPrivateMethod();
|
||
$privateMethod();
|
||
// Fatal error: Call to private method Foo::privateMethod() from global scope
|
||
// 这是因为调用是在 Foo 外部执行的,并且将从此时检查可见性。
|
||
|
||
class Foo1 {
|
||
public function getPrivateMethod() {
|
||
// Uses the scope where the callable is acquired.
|
||
return $this->privateMethod(...); // identical to Closure::fromCallable([$this, 'privateMethod']);
|
||
}
|
||
|
||
private function privateMethod() {
|
||
echo __METHOD__, "\n";
|
||
}
|
||
}
|
||
|
||
$foo1 = new Foo1;
|
||
$privateMethod = $foo1->getPrivateMethod();
|
||
$privateMethod(); // Foo1::privateMethod
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
|
||
</para>
|
||
|
||
<note>
|
||
<para>
|
||
不支持通过此语法(例如 <code>new Foo(...)</code>)创建对象,因为不会视 <code>new Foo()</code> 语法为调用。
|
||
</para>
|
||
</note>
|
||
|
||
<note>
|
||
<para>
|
||
一级可调用语法不能与 <link linkend="language.oop5.basic.nullsafe">nullsafe 运算符</link>结合使用。以下两种情况都会导致编译时错误:
|
||
<informalexample>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
$obj?->method(...);
|
||
$obj?->prop->method(...);
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</informalexample>
|
||
</para>
|
||
</note>
|
||
</sect1>
|
||
|
||
</chapter>
|
||
|
||
<!-- Keep this comment at the end of the file
|
||
Local variables:
|
||
mode: sgml
|
||
sgml-omittag:t
|
||
sgml-shorttag:t
|
||
sgml-minimize-attributes:nil
|
||
sgml-always-quote-attributes:t
|
||
sgml-indent-step:1
|
||
sgml-indent-data:t
|
||
indent-tabs-mode:nil
|
||
sgml-parent-document:nil
|
||
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
|
||
sgml-exposed-tags:nil
|
||
sgml-local-catalogs:nil
|
||
sgml-local-ecat-files:nil
|
||
End:
|
||
vim600: syn=xml fen fdm=syntax fdl=2 si
|
||
vim: et tw=78 syn=sgml
|
||
vi: ts=1 sw=1
|
||
-->
|