PHP Conference Japan 2024

函數參數與引數

函數參數是在函數簽章中宣告的。資訊可透過引數列表傳遞給函數,引數列表是以逗號分隔的運算式列表。引數從左到右求值,結果會指派給函數的參數,然後才實際呼叫函數(渴望求值)。

PHP 支援傳值(預設)、傳參考,以及預設引數值可變長度引數列表具名引數也受支援。

範例 #1 傳遞陣列給函數

<?php
function takes_array($input)
{
echo
"$input[0] + $input[1] = ", $input[0]+$input[1];
}
?>

自 PHP 8.0.0 起,函數參數列表可以包含一個尾隨逗號,這將被忽略。這在參數列表很長或包含長變數名稱的情況下特別有用,方便垂直列出參數。

範例 #2 帶有尾隨逗號的函數參數列表

<?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 之前不允許此尾隨逗號。
)
{
// ...
}
?>

以參考傳遞引數

預設情況下,函數引數是傳值傳遞(因此如果函數內引數的值被變更,則函數外的引數值不會被變更)。若要讓函數修改其引數,必須以參考傳遞。

要讓函數的引數始終以參考傳遞,請在函數定義中,將 & 符號加在參數名稱前面。

範例 #3 以參考傳遞函數引數

<?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不會指派預設值。

範例 #4 在函數中使用預設參數

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

預設參數值可以是純量值、arrays、特殊類型 null,以及自 PHP 8.1.0 起,使用 new ClassName() 語法的物件。

範例 #5 使用非純量類型作為預設值

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

範例 #6 使用物件作為預設值(自 PHP 8.1.0 起)

<?php
class DefaultCoffeeMaker {
public function
brew() {
return
"正在製作咖啡。\n";
}
}
class
FancyCoffeeMaker {
public function
brew() {
return
"為您精心製作一杯美好的咖啡。\n";
}
}
function
makecoffee($coffeeMaker = new DefaultCoffeeMaker)
{
return
$coffeeMaker->brew();
}
echo
makecoffee();
echo
makecoffee(new FancyCoffeeMaker);
?>

上面的範例將會輸出

Making coffee.
Crafting a beautiful coffee just for you.

預設值必須是常數表達式,而不是(例如)變數、類別成員或函式呼叫。

請注意,任何可選參數都應在任何必要參數之後指定,否則它們不能從呼叫中省略。請考慮以下範例

範例 #7 預設函式參數的錯誤用法

<?php
function makeyogurt($container = "碗", $flavour)
{
return
"正在製作一$container$flavour口味的優格。\n";
}

echo
makeyogurt("覆盆莓"); // "覆盆莓" 是 $container,而不是 $flavour
?>

上面的範例將會輸出

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

現在,將上述內容與此比較

範例 #8 預設函式參數的正確用法

<?php
function makeyogurt($flavour, $container = "碗")
{
return
"正在製作一$container$flavour口味的優格。\n";
}

echo
makeyogurt("覆盆莓"); // "覆盆莓" 是 $flavour
?>

上面的範例將會輸出

Making a bowl of raspberry yogurt.

從 PHP 8.0.0 開始,具名引數可用於跳過多個可選參數。

範例 #9 預設函式參數的正確用法

<?php
function makeyogurt($container = "碗", $flavour = "覆盆莓", $style = "希臘式")
{
return
"正在製作一$container$flavour $style 優格。\n";
}

echo
makeyogurt(style: "原味");
?>

上面的範例將會輸出

Making a bowl of raspberry natural yogurt.

從 PHP 8.0.0 開始,在可選參數之後宣告必要參數已棄用。這通常可以透過刪除預設值來解決,因為它永遠不會被使用。此規則的一個例外是形式為 Type $param = null 的參數,其中 null 預設值會使類型隱式可為 null。從 PHP 8.4.0 開始,此用法已棄用,應改為使用明確的可為 null 的類型

範例 #10 在必要參數之後宣告可選參數

<?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 透過使用 ... 符號支援使用者定義函式中的可變長度引數清單。

參數清單可以包含 ... 符號,表示函式接受可變數量的引數。這些引數將以 陣列 的形式傳遞到給定的變數中

範例 #11 使用 ... 來存取可變引數

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

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

上面的範例將會輸出

10

在呼叫函式時,... 也可以用來將 陣列Traversable 變數或常值解包到引數清單中

範例 #12 使用 ... 來提供引數

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

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

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

上面的範例將會輸出

3
3

您可以在 ... 符號之前指定正常的按位置參數。在這種情況下,只有不符合按位置引數的尾隨引數才會被新增到由 ... 產生的陣列中。

也可以在 ... 符號之前新增類型宣告。如果存在,則 ... 捕獲的所有引數都必須符合該參數類型。

範例 #13 類型宣告的可變引數

<?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 引入了具名引數,作為現有位置參數的擴展。具名引數允許根據參數名稱而不是參數位置將引數傳遞給函式。這使得引數的含義自我說明,使引數的順序無關緊要,並允許任意跳過預設值。

具名引數的傳遞方式是在值前面加上參數名稱,後面跟一個冒號。允許使用保留關鍵字作為參數名稱。參數名稱必須是識別字,不允許動態指定。

範例 #14 具名引數語法

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

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

範例 #15 位置引數與具名引數

<?php
// 使用位置引數:
array_fill(0, 100, 50);

// 使用具名引數:
array_fill(start_index: 0, count: 100, value: 50);
?>

具名引數的傳遞順序無關緊要。

範例 #16 與上方範例相同,但參數順序不同

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

具名引數可以與位置引數結合使用。在這種情況下,具名引數必須在位置引數之後。也可以只指定函式的某些選用引數,而不管它們的順序如何。

範例 #17 將具名引數與位置引數結合使用

<?php
htmlspecialchars
($string, double_encode: false);
// 與以下相同
htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8', false);
?>

多次將引數傳遞給同一個具名參數會導致 Error 例外。

範例 #18 多次將引數傳遞給同一個具名參數時擲回錯誤

<?php

function foo($param) { ... }

foo(param: 1, param: 2);
// 錯誤:具名參數 $param 會覆寫先前的引數

foo(1, param: 2);
// 錯誤:具名參數 $param 會覆寫先前的引數

?>

從 PHP 8.1.0 開始,可以在解包引數後使用具名引數。具名引數不得覆寫已解包的引數。

範例 #19 在解包後使用具名引數

<?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)); // 嚴重錯誤。具名參數 $b 會覆寫先前的引數
?>
新增註解

使用者提供的註解 8 則註解

131
php at richardneill dot org
9 年前
為了實驗傳參考和傳值的效能,我使用了這個腳本。結論如下。

#!/usr/bin/php
<?php
function sum($array,$max){ // 參考用,使用:「&$array」
$sum=0;
for (
$i=0; $i<2; $i++){
#$array[$i]++; // 取消註解此行以修改函數內的陣列。
$sum += $array[$i];
}
return (
$sum);
}

$max = 1E7 // 1000 萬個資料點。
$data = range(0,$max,1);

$start = microtime(true);
for (
$x = 0 ; $x < 100; $x++){
$sum = sum($data, $max);
}
$end = microtime(true);
echo
"Time: ".($end - $start)." s\n";

/* 執行時間:
# 傳遞方式 修改? 時間
- ------- --------- ----
1 值傳遞 否 56 us
2 參考傳遞 否 58 us

3 值傳遞 是 129 s
4 參考傳遞 是 66 us

結論:

1. PHP 在零複製/寫入時複製方面已經很聰明。函數呼叫不會複製資料,除非需要複製;資料只會在寫入時複製。這就是為什麼 #1 和 #2 花費的時間相似,而 #3 比 #4 花費的時間長 200 萬倍的原因。
[您永遠不需要使用 &$array 來要求編譯器執行零複製優化;它可以自行判斷。]

2. 您可以使用 &$array 來告訴編譯器「函數可以覆寫我的參數,我不再需要原始參數」。當我們有大量記憶體需要複製時,這可能會對效能產生巨大影響。
(這是 C 中唯一的方式,陣列總是作為指標傳遞)

3. & 的另一個用途是指定資料應該*回傳*的位置。(例如,exec() 所使用的)。
(這是一種 C 語言風格的傳遞指標作為輸出的方式,而 PHP 函數通常會回傳複雜類型,或在陣列中回傳多個答案)

4. 只有函數定義有 & 是沒有幫助的。呼叫者應該也有,至少作為語法糖。否則會導致程式碼難以閱讀:因為讀取函數呼叫的人不會預期它是以傳參考方式傳遞的。目前,必須以註解撰寫傳參考的函數呼叫,如下所示:
$sum = sum($data,$max); // 警告,$data 以傳參考方式傳遞,可能會被修改。

5. 有時,傳參考應該由呼叫者選擇,而不是函數定義。PHP 不允許這樣做,但讓呼叫者決定以參考方式傳遞資料將是有意義的。也就是說,「我不再需要這個變數了,可以覆寫記憶體」。
*/
?>
13
Simmo at 9000 dot 000
2 年前
對於剛開始接觸 PHP 或正在搜尋以了解此頁面描述為「... token」在可變長度參數中的含義的任何人
https://php.dev.org.tw/manual/en/functions.arguments.php#functions.variable-arg-list
<?php

func
($a, ...$b)

?>
這 3 個點,或省略符號,或「...」,或點點點,在其他語言中,有時稱為「展開運算符」。

由於這只在函數參數中使用,因此在技術上可能不是 PHP 中的真正運算符。(至少在 8.1 版本中?)

(由於它有一個難以搜尋的名稱,例如「... token」,我希望這個註解對某人有幫助)。
18
LilyWhite
3 年前
值得注意的是,您可以使用函數作為函數參數

<?php
function run($op, $a, $b) {
return
$op($a, $b);
}

$add = function($a, $b) {
return
$a + $b;
};

$mul = function($a, $b) {
return
$a * $b;
};

echo
run($add, 1, 2), "\n";
echo
run($mul, 1, 2);
?>

輸出
3
2
16
Hayley Watson
7 年前
與在函數宣告中使用 ... 來宣告可變參數相比,在函數呼叫中使用 ... 來提供多個參數的限制較少。特別是,它可以多次用於解壓縮參數,前提是所有此類用法都出現在任何位置參數之後。

<?php

$array1
= [[1],[2],[3]];
$array2 = [4];
$array3 = [[5],[6],[7]];

$result = array_merge(...$array1); // 合法,當然:$result == [1,2,3];
$result = array_merge($array2, ...$array1); // $result == [4,1,2,3]
$result = array_merge(...$array1, $array2); // 致命錯誤:在參數解壓縮後不能使用位置參數。
$result = array_merge(...$array1, ...$array3); // 合法! $result == [1,2,3,5,6,7]
?>

上面錯誤案例的正確做法是 $result==[1,2,3,4],但目前(v7.1.8)尚不支援。
28
gabriel at figdice dot org
8 年前
函數的參數如果是物件,則即使您不需要以傳參考方式傳遞,其屬性也會被函數修改。

<?php
$x
= new stdClass();
$x->prop = 1;

function
f ( $o ) // 請注意,沒有 &
{
$o->prop ++;
}

f($x);

echo
$x->prop; // 顯示:2
?>

這對於陣列來說是不同的

<?php
$y
= [ 'prop' => 1 ];

function
g( $a )
{
$a['prop'] ++;
echo
$a['prop']; // 顯示:2
}

g($y);

echo
$y['prop']; // 顯示:1
?>
12
boan dot web at outlook dot com
6 年前
引述

「如果將參數的預設值設定為 NULL,則可以使宣告接受 NULL 值。」

但是您可以這樣做(PHP 7.1+)

<?php
function foo(?string $bar) {
//...
}

foo(); // 致命錯誤
foo(null); // 正常
foo('Hello world'); // 正常
?>
4
Luna
1 年前
當使用具名參數並僅為部分參數新增預設值時,具有預設值的參數必須在最後指定,否則 PHP 會拋出錯誤

<?php

function test1($a, $c, $b = 2)
{
return
$a + $b + $c;
}

function
test2($a, $b = 2, $c)
{
return
$a + $b + $c;
}

echo
test1(a: 1, c: 3)."\n"; // Works
echo test2(a: 1, c: 3)."\n"; // ArgumentCountError: Argument #2 ($b) not passed

?>

我假設這狀況是因為 PHP 內部將呼叫改寫成類似 test1(1, 3) 和 test2(1, , 3)。第一個呼叫是有效的,但第二個顯然不是。
5
Hayley Watson
7 年前
如果你在函式的參數列表中使用 ...,基於顯而易見的原因,你只能使用一次。比較不明顯的是它必須放在最後一個參數;正如手冊所說:「你可以在 ... 符號之前指定一般的定位參數。(強調為我所加)」。

<?php
function variadic($first, ...$most, $last)
{
/*等等*/}

variadic(1, 2, 3, 4, 5);
?>
會導致嚴重的錯誤,即使它看起來應該是將 $first 設定為 1,$most 設定為 [2, 3, 4],而 $last 設定為 5 的「最佳做法」。
To Top