参数与参数值
概述
函数参数是函数与外部通信的桥梁。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<script> [email] => alice@test.com [age] => 30 )注意事项
常见错误
- 默认参数位置错误:默认值参数必须在非默认值参数之后
php
<?php
declare(strict_types=1);
// 错误:默认值参数在非默认值参数之前
// function test($a = 1, $b) {}
// 正确
function test(int $b, int $a = 1): int
{
return $a + $b;
}- 引用传递的限制:只有变量可以按引用传递,字面量或表达式不行
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- 严格模式下的 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),
];
}