PHP 函数与作用域
AI 辅助生成本文由 AI 起草并经人工核查,示例均标明软件版本;发现错误请以官方文档为准。
PHP 8 的类型系统已经相当完整:参数、返回值、属性都能声明类型。写好类型声明,能把大量运行时错误提前暴露。
定义与调用
function greet(string $name): string
{
return "你好," . $name;
}
echo greet("ann"); // 你好,ann
函数可以在定义前调用(函数声明会被提升),但实际项目中通常用 Composer 自动加载,调用顺序并不重要。
参数类型与返回值类型
function add(int $a, int $b): int
{
return $a + $b;
}
function isAdult(int $age): bool
{
return $age >= 18;
}
function findUser(int $id): ?array // ?array 表示"数组或 null"
{
return $id > 0 ? ["id" => $id] : null;
}
常用类型写法:
| 写法 | 含义 |
|---|---|
int float string bool |
标量类型 |
array |
数组 |
?string |
string 或 null |
| `int | string` |
void |
无返回值(只能用于返回类型) |
never |
函数不会返回(必然抛异常或退出) |
callable |
可调用的回调 |
配合 declare(strict_types=1); 使用,传错类型直接抛 TypeError;不声明时 PHP 会尝试自动转换(见本系列第 1 篇)。
默认值与命名参数
function createUser(string $name, string $role = "user", bool $active = true): array
{
return ["name" => $name, "role" => $role, "active" => $active];
}
带默认值的参数必须放在不带默认值的参数之后。
PHP 8 支持命名参数,可以跳过中间参数、且不依赖顺序:
createUser(name: "ann", active: false);
参数较多或布尔开关多时,命名参数能大幅提升可读性。
可变参数
function sum(int ...$numbers): int
{
return array_sum($numbers);
}
sum(1, 2, 3); // 6
调用时也可以用 ... 展开数组:
$nums = [1, 2, 3];
sum(...$nums); // 6
传值与传引用
默认按值传递,函数内修改不影响外部:
function bump(int $n): void
{
$n++;
}
$count = 1;
bump($count);
echo $count; // 1,没变
需要修改原值时用 & 声明引用参数:
function bump(int &$n): void
{
$n++;
}
$count = 1;
bump($count);
echo $count; // 2
对象是个例外:对象变量持有的是句柄,函数内修改对象属性会影响外部(不需要 &):
function renameUser(object $user): void
{
$user->name = "bob"; // 外部对象也被改了
}
引用参数会让调用方难以预判副作用,除非明确需要(例如递归收集结果),否则优先返回值。
作用域
函数内部不能访问外部变量:
$tax = 0.13;
function total(float $price): float
{
return $price * (1 + $tax); // Warning: Undefined variable $tax
}
三种做法,推荐第一种:
// 1. 参数传递(推荐)
function total(float $price, float $tax): float
{
return $price * (1 + $tax);
}
// 2. global 声明(不推荐,难追踪)
function total(float $price): float
{
global $tax;
return $price * (1 + $tax);
}
// 3. 常量(适合真正的全局配置)
const TAX_RATE = 0.13;
function total(float $price): float
{
return $price * (1 + TAX_RATE);
}
超全局变量($_GET、$_POST、$_SERVER 等)在函数内可直接访问,但更建议把值作为参数传入,便于测试。
static 变量
函数内的 static 变量在多次调用之间保持值:
function counter(): int
{
static $count = 0;
return ++$count;
}
counter(); // 1
counter(); // 2
适合缓存一次性计算结果,但会让函数有“隐藏状态”,多数情况下用参数或类属性更清晰。
匿名函数与箭头函数
$numbers = [1, 2, 3];
// 箭头函数(PHP 7.4+):自动捕获外部变量,只能有一个表达式
$doubled = array_map(fn($n) => $n * 2, $numbers);
// 匿名函数:需要 use 显式捕获
$factor = 10;
$scaled = array_map(function ($n) use ($factor) {
return $n * $factor;
}, $numbers);
use 默认按值捕获,需要修改外部变量时用 use (&$total)。
一个完整例子:输入校验
<?php
declare(strict_types=1);
function validateUser(array $input): array
{
$errors = [];
$name = trim($input["name"] ?? "");
if (mb_strlen($name, "UTF-8") < 2) {
$errors[] = "用户名至少 2 个字符";
}
$age = filter_var($input["age"] ?? null, FILTER_VALIDATE_INT);
if ($age === false || $age < 0 || $age > 150) {
$errors[] = "年龄必须是 0-150 的整数";
}
return ["valid" => $errors === [], "errors" => $errors];
}
var_dump(validateUser(["name" => "a", "age" => "200"]));
// ["valid" => false, "errors" => ["用户名至少 2 个字符", "年龄必须是 0-150 的整数"]]
注意 filter_var 返回 false 表示校验失败,返回 0 是合法值,所以判断必须用 === false。
常见错误与注意事项
- 带默认值的参数放在前面:会导致后面的必填参数无法省略,PHP 会给出弃用警告。
- 用
== false判断filter_var/strpos结果:0会被误判,必须=== false。 - 函数内直接使用外部变量:拿不到值,用参数或常量。
- 以为对象传参是复制:对象修改会影响外部。
- 匿名函数忘记
use:闭包内访问不到外部变量。改用箭头函数fn()可自动捕获。 - 引用参数造成意外副作用:调用方看不出函数会改原值,优先返回值。
小结
- 参数与返回值都写类型,配合
declare(strict_types=1);。 ?type表示可空,A|B表示联合类型,void表示无返回。- 可变参数用
...,调用时也能用...展开数组。 - 函数不共享外部作用域:用参数传递,配置用常量。
- 短回调优先箭头函数
fn()。