Skip to content

参数与参数值

概述

函数参数是函数与外部通信的桥梁。PHP 支持多种参数传递方式,包括位置参数、默认值、类型声明、值传递和引用传递。理解参数机制是编写健壮函数的关键。

PHP 版本说明

  • 类型声明自 PHP 7.0 起全面支持标量类型
  • declare(strict_types=1) 自 PHP 7.0 引入
  • 联合类型自 PHP 8.0 引入
  • 交叉类型自 PHP 8.1 引入
  • readonly 属性类型在参数中不适用,但 readonly 类属性自 PHP 8.1 引入

基础概念

参数分类

PHP 函数参数可以从不同维度分类:

分类维度类型说明
按位置位置参数按顺序传递,最常用的方式
按默认值必需参数 / 可选参数有默认值的参数为可选
按传递方式值传递 / 引用传递默认为值传递
按类型有类型声明 / 无类型声明PHP 7.0+ 推荐声明类型
按数量固定参数 / 可变参数可变参数使用 ... 运算符

参数传递机制

PHP 默认使用值传递(pass by value),即函数内部收到的是参数值的副本。也可以使用 引用传递(pass by reference),让函数直接操作原始变量。

语法与代码

位置参数

位置参数是最基本的方式,调用时按定义顺序依次传入。

php
<?php

declare(strict_types=1);

function createUser(
    string $name,
    string $email,
    int $age
): array {
    return [
        'name' => $name,
        'email' => $email,
        'age' => $age,
    ];
}

// 按位置顺序传入参数
$user = createUser('Alice', 'alice@example.com', 30);
print_r($user);
// Array ( [name] => Alice [email] => alice@example.com [age] => 30 )

默认参数值

为参数设置默认值后,调用时可以省略该参数。默认值必须是常量表达式(字面量、常量、数组等),不能是变量或函数调用。

php
<?php

declare(strict_types=1);

// 默认值必须是常量表达式
function connectToDatabase(
    string $host = 'localhost',
    int $port = 3306,
    string $charset = 'utf8mb4'
): string {
    return "mysql://{$host}:{$port}?charset={$charset}";
}

echo connectToDatabase() . "\n";
// mysql://localhost:3306?charset=utf8mb4

echo connectToDatabase('192.168.1.100', 5432) . "\n";
// mysql://192.168.1.100:5432?charset=utf8mb4

// 默认值可以使用常量
const DEFAULT_TIMEOUT = 30;

function fetchData(string $url, int $timeout = DEFAULT_TIMEOUT): string
{
    return "Fetching {$url} with timeout {$timeout}s";
}

注意事项

所有默认值参数必须放在非默认值参数的右侧。PHP 8.0 引入的命名参数可以绕过此限制,但位置传参时仍需遵守此规则。

类型声明

为参数添加类型声明,让 PHP 在调用时自动进行类型检查。

php
<?php

declare(strict_types=1);

// 基本类型声明
function setAge(int $age): void
{
    echo "Age is set to: {$age}\n";
}

setAge(25);       // 正常
// setAge('abc');  // TypeError in strict mode

// 联合类型(PHP 8.0+)
function processInput(int|string $input): string
{
    return "Received: " . (is_int($input) ? "integer {$input}" : "string {$input}");
}

echo processInput(42) . "\n";       // Received: integer 42
echo processInput('hello') . "\n"; // Received: string hello

// 交叉类型(PHP 8.1+)
function processCountable(iterable&Countable $items): int
{
    return count($items);
}

// Nullable 类型
function findUser(?int $id): ?string
{
    if ($id === null) {
        return null;
    }

    return "User #{$id}";
}

echo findUser(null) . "\n";  // 无输出(返回 null)
echo findUser(1) . "\n";    // User #1

值传递与引用传递

php
<?php

declare(strict_types=1);

// 值传递(默认行为):函数内修改不影响外部变量
function incrementByValue(int $n): int
{
    $n += 10;

    return $n;
}

$value = 5;
incrementByValue($value);
echo $value . "\n"; // 输出: 5(未改变)

// 引用传递:函数内修改直接影响外部变量
function incrementByReference(int &$n): void
{
    $n += 10;
}

$number = 5;
incrementByReference($number);
echo $number . "\n"; // 输出: 15(已改变)

// 引用传递的实际应用:交换变量
function swap(mixed &$a, mixed &$b): void
{
    $temp = $a;
    $a = $b;
    $b = $temp;
}

$x = 'hello';
$y = 'world';
swap($x, $y);
echo "{$x} {$y}\n"; // 输出: world hello

参数个数可变性

传统方式使用 func_get_args() 等函数获取可变参数,现代 PHP 推荐使用 ... 运算符。

php
<?php

declare(strict_types=1);

// 传统方式(不推荐)
function sumOld(): int
{
    $args = func_get_args();
    $total = 0;

    foreach ($args as $arg) {
        $total += $arg;
    }

    return $total;
}

echo sumOld(1, 2, 3, 4, 5) . "\n"; // 输出: 15

// 现代方式:使用 ... 运算符(PHP 5.6+)
function sum(int ...$numbers): int
{
    return array_sum($numbers);
}

echo sum(1, 2, 3, 4, 5) . "\n"; // 输出: 15

// 混合固定参数和可变参数
function format(string $template, mixed ...$values): string
{
    foreach ($values as $value) {
        $template = preg_replace('/%s/', (string) $value, $template, 1);
    }

    return $template;
}

echo format('Hello %s, you have %s messages', 'Alice', 3) . "\n";
// 输出: Hello Alice, you have 3 messages

详细说明

参数求值顺序

PHP 按从左到右的顺序对参数进行求值。这意味着在参数表达式中可以使用前面的参数值(但不推荐依赖此行为)。

php
<?php

declare(strict_types=1);

function divide(int $numerator, int $divisor): float
{
    if ($divisor === 0) {
        throw new RuntimeException('Division by zero');
    }

    return $numerator / $divisor;
}

// 参数从左到右求值
$a = 10;
$b = 2;
echo divide($a, $b) . "\n"; // 输出: 5

类型强制转换与严格模式

declare(strict_types=1) 下,PHP 不会对参数进行隐式类型转换。非严格模式下,PHP 会尝试将传入值转换为声明的类型。

php
<?php

// 非严格模式(默认)
function acceptInt(int $value): int
{
    return $value * 2;
}

echo acceptInt('42') . "\n";    // 输出: 84(字符串 '42' 被隐式转换为 int)
echo acceptInt(3.14) . "\n";    // 输出: 6(float 被截断为 int)

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

function acceptStrictInt(int $value): int
{
    return $value * 2;
}

try {
    acceptStrictInt('42'); // TypeError: Argument #1 ($value) must be of type int, string given
} catch (TypeError $e) {
    echo $e->getMessage() . "\n";
}

建议

始终在文件顶部使用 declare(strict_types=1) 启用严格模式,以获得更好的类型安全性和更早的错误发现。

特殊类型:callable 和 iterable

php
<?php

declare(strict_types=1);

// callable 类型:接受可调用的值
function executeCallback(callable $callback, mixed ...$args): mixed
{
    return $callback(...$args);
}

$result = executeCallback('strtoupper', 'hello');
echo $result . "\n"; // 输出: HELLO

$result = executeCallback(fn(int $a, int $b) => $a + $b, 3, 4);
echo $result . "\n"; // 输出: 7

// iterable 类型:接受数组或 Traversable 对象
function printItems(iterable $items): void
{
    foreach ($items as $key => $value) {
        echo "{$key}: {$value}\n";
    }
}

printItems(['name' => 'Alice', 'age' => 30]);
printItems(new ArrayIterator(['x' => 1, 'y' => 2]));

实战示例

带类型安全的配置项解析

php
<?php

declare(strict_types=1);

function parseConfig(array $config): array
{
    $defaults = [
        'host' => 'localhost',
        'port' => 3306,
        'username' => 'root',
        'password' => '',
        'timeout' => 30,
        'charset' => 'utf8mb4',
    ];

    return array_merge($defaults, $config);
}

function validateConfig(array $config): void
{
    $required = ['host', 'username'];

    foreach ($required as $field) {
        if (empty($config[$field])) {
            throw new InvalidArgumentException("Config field '{$field}' is required");
        }
    }

    if (!is_int($config['port']) || $config['port'] < 1 || $config['port'] > 65535) {
        throw new InvalidArgumentException('Port must be an integer between 1 and 65535');
    }
}

// 使用示例
$config = parseConfig([
    'host' => '192.168.1.100',
    'username' => 'admin',
    'password' => 'secret',
    'port' => 5432,
]);

validateConfig($config);
print_r($config);

引用传递修改数组元素

php
<?php

declare(strict_types=1);

function sanitizeString(string &$value): void
{
    $value = trim($value);
    $value = stripslashes($value);
    $value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}

function sanitizeArray(array &$data): void
{
    foreach ($data as $key => &$value) {
        if (is_string($value)) {
            sanitizeString($value);
        }
    }
    unset($value); // 断开引用,避免后续问题
}

// 使用示例
$input = [
    'name' => '  Alice<script>  ',
    'email' => 'alice@test.com',
    'age' => 30,
];

sanitizeArray($input);
print_r($input);
// Array ( [name] => Alice&lt;script&gt; [email] => alice@test.com [age] => 30 )

注意事项

常见错误

  1. 默认参数位置错误:默认值参数必须在非默认值参数之后
php
<?php

declare(strict_types=1);

// 错误:默认值参数在非默认值参数之前
// function test($a = 1, $b) {}

// 正确
function test(int $b, int $a = 1): int
{
    return $a + $b;
}
  1. 引用传递的限制:只有变量可以按引用传递,字面量或表达式不行
php
<?php

declare(strict_types=1);

function modify(int &$value): void
{
    $value *= 2;
}

$x = 10;
modify($x);       // 正确:$x 是变量
echo $x . "\n";  // 输出: 20

// modify(10);    // Fatal error: Cannot pass parameter by reference
// modify($x+1);  // Fatal error
  1. 严格模式下的 null 传递
php
<?php

declare(strict_types=1);

function processString(string $value): string
{
    return strtoupper($value);
}

// processString(null); // TypeError
// 解决方案:使用 ?string 或 string|null(PHP 8.0+)
function processNullableString(?string $value): string
{
    return $value !== null ? strtoupper($value) : '';
}

最佳实践

1. 参数从宽到窄排列

将最通用的参数放在前面,更具体的参数放在后面。

php
<?php

declare(strict_types=1);

// 推荐
function query(
    string $sql,
    array $params = [],
    int $fetchMode = PDO::FETCH_ASSOC,
    bool $retry = false
): array {
    // ...
}

// 不推荐:参数顺序随意
// function query(bool $retry, string $sql, array $params = [], int $fetchMode = PDO::FETCH_ASSOC): array {}

2. 避免过多参数

超过 3-4 个参数时,考虑使用数组或 DTO(数据传输对象)。

php
<?php

declare(strict_types=1);

// 参数过多时,使用配置数组
function sendEmail(array $config): bool
{
    $required = ['to', 'subject', 'body'];

    foreach ($required as $field) {
        if (empty($config[$field])) {
            throw new InvalidArgumentException("Field '{$field}' is required");
        }
    }

    $from = $config['from'] ?? 'noreply@example.com';
    $cc = $config['cc'] ?? [];
    $html = $config['html'] ?? false;

    // 发送逻辑...
    return true;
}

sendEmail([
    'to' => 'user@example.com',
    'subject' => 'Hello',
    'body' => 'Welcome!',
    'from' => 'admin@example.com',
    'cc' => ['manager@example.com'],
]);

3. 使用严格模式和类型声明

始终为参数添加类型声明,并在文件顶部启用严格模式。

4. 引用传递要有明确意图

引用传递会引入副作用,使代码难以理解和测试。仅在确实需要修改外部变量时使用。

php
<?php

declare(strict_types=1);

// 引用传递适用场景:确实需要修改原始变量
function appendToFile(string $filename, string $content): int
{
    $bytes = file_put_contents($filename, $content, FILE_APPEND);

    return $bytes !== false ? $bytes : 0;
}

// 不推荐:用引用传递来"返回"多个值
// function badExample(array $data, &$count, &$sum): void { ... }

// 推荐:返回数组或对象
function goodExample(array $data): array
{
    return [
        'count' => count($data),
        'sum' => array_sum($data),
    ];
}

参考链接