PHP 函數 (Function)

函數是一段可重複使用的程式碼區塊,用來執行特定的任務。函數讓程式碼更有組織、更容易維護和重複使用。

定義函數

使用 function 關鍵字定義函數:

<?php
function greet() {
    echo "Hello, World!";
}

// 呼叫函數
greet();  // Hello, World!
greet();  // 可以重複呼叫
?>

函數命名規則

  • 函數名稱不區分大小寫(但建議保持一致)
  • 必須以字母或底線開頭
  • 可以包含字母、數字和底線
  • 不能與現有的函數或 PHP 保留字同名
<?php
// 有效的函數名稱
function myFunction() {}
function my_function() {}
function _privateFunction() {}
function function2() {}

// 這三個呼叫的是同一個函數
myFunction();
MyFunction();
MYFUNCTION();
?>

函數參數

函數可以接收參數來處理不同的資料:

<?php
function greet($name) {
    echo "Hello, $name!";
}

greet("Alice");  // Hello, Alice!
greet("Bob");    // Hello, Bob!
?>

多個參數

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

echo add(3, 5);  // 8
echo add(10, 20);  // 30
?>

預設參數值

<?php
function greet($name, $greeting = "Hello") {
    echo "$greeting, $name!";
}

greet("Alice");           // Hello, Alice!
greet("Bob", "Hi");       // Hi, Bob!
greet("Charlie", "Hey");  // Hey, Charlie!
?>

有預設值的參數必須放在沒有預設值的參數後面:

// 正確
function test($a, $b = 10) {}

// 錯誤(會產生警告) function test($a = 10, $b) {}

參數型別宣告 (PHP 7+)

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

echo add(3, 5);    // 8
echo add(3.7, 5);  // 8(float 會被轉換為 int)

// 嚴格模式
declare(strict_types=1);

function multiply(int $a, int $b): int {
    return $a * $b;
}

echo multiply(3, 5);    // 15
echo multiply(3.7, 5);  // TypeError(嚴格模式下不會自動轉換)
?>

可用的型別宣告

型別說明PHP 版本
int整數7.0+
float浮點數7.0+
string字串7.0+
bool布林值7.0+
array陣列7.0+
callable可呼叫7.0+
iterable可迭代7.1+
object物件7.2+
mixed任意型別8.0+
?type可為 null7.1+
type|type聯合型別8.0+
<?php
// 可為 null 的參數
function findUser(?int $id): ?array {
    if ($id === null) {
        return null;
    }
    return ['id' => $id, 'name' => 'User'];
}

// 聯合型別 (PHP 8+)
function process(int|float $number): int|float {
    return $number * 2;
}
?>

命名參數 (PHP 8+)

<?php
function createUser(string $name, int $age, string $city = 'Unknown') {
    return "Name: $name, Age: $age, City: $city";
}

// 傳統呼叫
echo createUser('Alice', 25, 'Taipei');

// 使用命名參數(可以任意順序)
echo createUser(age: 25, name: 'Bob');
echo createUser(name: 'Charlie', city: 'Kaohsiung', age: 30);
?>

回傳值

使用 return 回傳值:

<?php
function square($n) {
    return $n * $n;
}

$result = square(5);
echo $result;  // 25

// return 也可以用來提早結束函數
function divide($a, $b) {
    if ($b === 0) {
        return null;  // 提早返回
    }
    return $a / $b;
}
?>

回傳型別宣告

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

function getUser(): array {
    return ['name' => 'Alice', 'age' => 25];
}

// 可為 null 的回傳型別
function findById(int $id): ?string {
    return $id > 0 ? "User $id" : null;
}

// void - 不回傳任何值
function logMessage(string $message): void {
    echo "[LOG] $message\n";
    // 不能有 return 值
}

// never - 函數永不正常返回 (PHP 8.1+)
function throwError(): never {
    throw new Exception("Error");
}
?>

回傳多個值

PHP 函數只能回傳一個值,但可以使用陣列回傳多個值:

<?php
function getMinMax(array $numbers): array {
    return [
        'min' => min($numbers),
        'max' => max($numbers)
    ];
}

$result = getMinMax([3, 1, 4, 1, 5, 9]);
echo $result['min'];  // 1
echo $result['max'];  // 9

// 使用解構
['min' => $min, 'max' => $max] = getMinMax([3, 1, 4, 1, 5, 9]);
echo "$min, $max";  // 1, 9
?>

傳值 vs 傳址

傳值(預設)

參數的值會被複製,函數內的修改不會影響原變數:

<?php
function addOne($num) {
    $num += 1;
    return $num;
}

$x = 5;
$result = addOne($x);

echo $x;       // 5(原變數不變)
echo $result;  // 6
?>

傳址(使用 &)

參數是原變數的參考,函數內的修改會影響原變數:

<?php
function addOne(&$num) {
    $num += 1;
}

$x = 5;
addOne($x);

echo $x;  // 6(原變數被修改)
?>

可變數量參數

使用 ... (Spread Operator)

<?php
function sum(...$numbers): int {
    return array_sum($numbers);
}

echo sum(1, 2, 3);        // 6
echo sum(1, 2, 3, 4, 5);  // 15

// 可以加上型別
function sumInts(int ...$numbers): int {
    return array_sum($numbers);
}
?>

搭配固定參數

<?php
function greetAll(string $greeting, string ...$names): void {
    foreach ($names as $name) {
        echo "$greeting, $name!\n";
    }
}

greetAll("Hello", "Alice", "Bob", "Charlie");
?>

展開陣列

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

$numbers = [1, 2, 3];
echo add(...$numbers);  // 6
?>

遞迴函數

函數可以呼叫自己:

<?php
function factorial(int $n): int {
    if ($n <= 1) {
        return 1;
    }
    return $n * factorial($n - 1);
}

echo factorial(5);  // 120 (5 × 4 × 3 × 2 × 1)
?>

費氏數列

<?php
function fibonacci(int $n): int {
    if ($n <= 1) {
        return $n;
    }
    return fibonacci($n - 1) + fibonacci($n - 2);
}

for ($i = 0; $i < 10; $i++) {
    echo fibonacci($i) . " ";
}
// 0 1 1 2 3 5 8 13 21 34
?>
遞迴要小心無限迴圈和堆疊溢出。確保有終止條件,並考慮效能問題。

函數是一級公民

PHP 中函數可以:

指派給變數

<?php
function greet($name) {
    return "Hello, $name!";
}

$sayHello = 'greet';  // 儲存函數名稱
echo $sayHello('Alice');  // Hello, Alice!
?>

作為參數傳遞

<?php
function applyTwice(callable $func, $value) {
    return $func($func($value));
}

function double($n) {
    return $n * 2;
}

echo applyTwice('double', 5);  // 20 (5 × 2 × 2)
?>

作為回傳值

<?php
function createMultiplier($factor) {
    return function($n) use ($factor) {
        return $n * $factor;
    };
}

$double = createMultiplier(2);
$triple = createMultiplier(3);

echo $double(5);  // 10
echo $triple(5);  // 15
?>

內建函數

PHP 提供大量內建函數:

<?php
// 字串函數
echo strlen("Hello");      // 5
echo strtoupper("hello");  // HELLO

// 陣列函數
$arr = [3, 1, 4, 1, 5];
echo count($arr);          // 5
echo max($arr);            // 5

// 數學函數
echo abs(-10);             // 10
echo round(3.7);           // 4

// 日期函數
echo date("Y-m-d");        // 2024-12-09
echo time();               // 時間戳記
?>

實際應用範例

表單驗證

<?php
function validateEmail(string $email): bool {
    return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}

function validatePassword(string $password): array {
    $errors = [];
    
    if (strlen($password) < 8) {
        $errors[] = "密碼至少需要 8 個字元";
    }
    
    if (!preg_match('/[A-Z]/', $password)) {
        $errors[] = "密碼需要包含大寫字母";
    }
    
    if (!preg_match('/[0-9]/', $password)) {
        $errors[] = "密碼需要包含數字";
    }
    
    return $errors;
}

$email = "test@example.com";
$password = "abc123";

if (!validateEmail($email)) {
    echo "Email 格式不正確\n";
}

$passwordErrors = validatePassword($password);
if (!empty($passwordErrors)) {
    foreach ($passwordErrors as $error) {
        echo "密碼錯誤:$error\n";
    }
}
?>

格式化輸出

<?php
function formatCurrency(float $amount, string $currency = 'TWD'): string {
    $symbols = ['TWD' => 'NT$', 'USD' => '$', 'EUR' => '€'];
    $symbol = $symbols[$currency] ?? $currency;
    
    return $symbol . number_format($amount, 2);
}

function formatDate(string $date, string $format = 'Y-m-d'): string {
    $timestamp = strtotime($date);
    return date($format, $timestamp);
}

echo formatCurrency(1234.5);                    // NT$1,234.50
echo formatCurrency(99.99, 'USD');              // $99.99
echo formatDate('2024-12-09', 'Y年m月d日');     // 2024年12月09日
?>