## 函数的参数和参数值

函数参数在函数签名中声明。通过参数值列表可以传递信息到函数，即以逗号作为分隔符的表达式列表。函数在实际调用之前，值参数是从左向右求值的（及早求值），并将结果赋值给函数的参数。

PHP 支持按值传递参数值（默认），通过引用传递参数以及默认参数。也支持可变长度参数列表和命名参数。

> 注意:

> 自 PHP 7.3.0 起，函数调用的参数列表中可以包含尾随逗号：

>     <?php
>     $v = foo(
>         $arg_1,
>         $arg_2,
>     );
>     ?>

自 PHP 8.0.0 起，函数参数列表可以包含一个尾部的逗号，这个逗号将被忽略。这在参数列表较长或包含较长的变量名的情况下特别有用，这样可以方便地垂直列出参数。

示例 #1 函数参数使用尾部逗号

    <?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 之前，这个尾部的逗号是不允许的。
    )
    {
        // ...
    }
    ?>

### 通过引用传递参数值

默认情况下，函数参数值通过值传递（因而即使在函数内部改变参数值，它并不会改变函数外部的值）。如果希望允许函数修改它的参数值，必须通过引用传递。

如果想要函数的参数值始终通过引用传递，可以在函数定义中该参数的前面加上符号 &：

示例 #2 用引用传递函数参数值

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

将常量表达式作为参数值传递给需要通过引用传递的参数是错误的。

### 默认参数的值

函数可以使用类似分配变量的语法定义参数的默认值。仅当参数未传递值时才使用默认值；注意传递 `null` 不会分配默认值。

示例 #3 在函数中使用默认参数

    <?php
    function makecoffee($type = "cappuccino")
    {
        return "Making a cup of $type.\n";
    }
    echo makecoffee();
    echo makecoffee(null);
    echo makecoffee("espresso");
    ?>

以上示例会输出：

    Making a cup of cappuccino.
    Making a cup of .
    Making a cup of espresso.

默认参数值可以是标量值、array、特殊类型 `null`，以及从 PHP 8.1.0 开始，使用 new ClassName() 语法的对象。

示例 #4 使用非标量类型作为默认参数

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

以上示例会输出：

    Making a cup of cappuccino with hands.
    Making a cup of cappuccino, lavazza with teapot.

示例 #5 使用对象作为默认值（自 PHP 8.1.0 起）

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

以上示例会输出：

    Making coffee.
    Crafting a beautiful coffee just for you.

默认值必须是常量表达式，不能是诸如变量，类成员，或者函数调用等。

注意任何可选参数都应在强制参数之后指定，否则可选参数不能在调用时省略。考虑以下示例：

示例 #6 函数默认参数的不正确用法

    <?php
    function makeyogurt($container = "bowl", $flavour)
    {
        return "Making a $container of $flavour yogurt.\n";
    }

    echo makeyogurt("raspberry"); // "raspberry" 是 $container, 不是 $flavour
    ?>

以上示例会输出：

    Fatal error: Uncaught ArgumentCountError: Too few arguments
     to function makeyogurt(), 1 passed in example.php on line 42

现在，比较上面的例子和这个例子：

示例 #7 函数默认参数正确的用法

    <?php
    function makeyogurt($flavour, $container = "bowl")
    {
        return "Making a $container of $flavour yogurt.\n";
    }

    echo makeyogurt("raspberry"); // "raspberry" 是 $flavour
    ?>

以上示例会输出：

    Making a bowl of raspberry yogurt.

自 PHP 8.0.0 起，命名参数可用于跳过多个可选参数。

示例 #8 函数默认参数正确的用法

    <?php
    function makeyogurt($container = "bowl", $flavour = "raspberry", $style = "Greek")
    {
        return "Making a $container of $flavour $style yogurt.\n";
    }

    echo makeyogurt(style: "natural");
    ?>

以上示例会输出：

    Making a bowl of raspberry natural yogurt.

自 PHP 8.0.0 起，弃用在可选参数之后声明强制参数。这通常可以通过删除默认值来解决，因为它永远不会被使用。唯一的例外是 `Type $param = null` 类型的参数，其中默认 `null` 使得该类型可以隐式为 null。自 PHP 8.4.0 起，此用法已弃用，而应改用显式可为 null 类型。

示例 #9 强制参数后声明可选参数

    <?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) {}       // 推荐

    ?>

> 注意: 自 PHP 7.1.0 起，省略未指定默认值的参数会原因引发 ArgumentCountError；在此之前的版本会引发警告。

> 注意: 期望通过引用传参的参数可以有默认值。

### 可变数量的参数值列表

PHP 在用户自定义函数中支持可变数量的参数值列表。由 `...` 语法实现。

参数列表可能包含 `...` 记号，表示该函数接受可变数量的参数值。参数值将作为 array 传递到指定变量中：

示例 #10 使用 `...` 来访问变量参数值

    <?php
    function sum(...$numbers) {
        $acc = 0;
        foreach ($numbers as $n) {
            $acc += $n;
        }
        return $acc;
    }

    echo sum(1, 2, 3, 4);
    ?>

以上示例会输出：

    10

`...` 也可以在调用函数时将 array 或 Traversable 变量或文字解包到参数值列表中使用：

示例 #11 使用 `...` 来传递参数

    <?php
    function add($a, $b) {
        return $a + $b;
    }

    echo add(...[1, 2])."\n";

    $a = [1, 2];
    echo add(...$a);
    ?>

以上示例会输出：

    3
    3

可以在 `...` 前指定正常的位置参数。在这种情况下，只有不符合位置参数的尾部参数才会被添加到 `...` 生成的数组中。

也可以在 `...` 标记前添加 类型声明。如果存在这种情况，那么 `...` 捕获的所有参数都必须匹配参数类型。

示例 #12 输入提示的变量参数

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

以上示例会输出：

    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

最后，可变参数值也可以通过在 `...` 前面加上 `&` 来通过引用传递。

### 命名参数

PHP 8.0.0 开始引入了命名参数作为现有位置参数的扩展。命名参数允许根据参数名而不是参数位置向函数传参。这使得参数的含义自成体系，参数与顺序无关，并允许任意跳过默认值。

命名参数通过在参数名前加上冒号来传递。允许使用保留关键字作为参数名。参数名必须是一个标识符，不允许动态指定。

示例 #13 命名参数的语法

    <?php
    myFunction(paramName: $value);
    array_foobar(array: $value);

    // 不支持。
    function_name($variableStoringParamName: $value);
    ?>

示例 #14 通过位置传参与命名参数的对比

    <?php
    // 使用顺序传递参数：
    array_fill(0, 100, 50);

    // 使用命名参数：
    array_fill(start_index: 0, count: 100, value: 50);
    ?>

指定参数的传递顺序并不重要。

示例 #15 参数顺序不同的示例（同上例）

    <?php
    array_fill(value: 50, count: 100, start_index: 0);
    ?>

命名参数也可以与位置参数相结合使用。此种情况下，命名参数必须在位置参数之后。也可以只指定一个函数的部分可选参数，而不考虑它们的顺序。

示例 #16 命名参数与位置参数结合使用

    <?php
    htmlspecialchars($string, double_encode: false);
    // 等价于
    htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8', false);
    ?>

传递多个参数值给同一个命名参数会导致 Error 异常。

示例 #17 传递多个参数值到相同命名参数将会导致抛出 Error

    <?php

    function foo($param) { ... }

    foo(param: 1, param: 2);
    // 错误：命名参数 $param 覆盖了之前的参数

    foo(1, param: 2);
    // 错误：命名参数 $param 覆盖了之前的参数

    ?>

自 PHP 8.1.0 起，可以在解包参数后面使用命名参数。命名参数不能覆盖已解包的参数。

示例 #18 解包后使用命名参数

    <?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 覆盖之前的参数
    ?>

`↑` and `↓` to navigate • `Enter` to select • `Esc` to close • `/` to open