PHP 函数与作用域

AI 辅助生成本文由 AI 起草并经人工核查,示例均标明软件版本;发现错误请以官方文档为准。

PHP 8.4

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。

常见错误与注意事项

  1. 带默认值的参数放在前面:会导致后面的必填参数无法省略,PHP 会给出弃用警告。
  2. 用 == false 判断 filter_var / strpos 结果:0 会被误判,必须 === false。
  3. 函数内直接使用外部变量:拿不到值,用参数或常量。
  4. 以为对象传参是复制:对象修改会影响外部。
  5. 匿名函数忘记 use:闭包内访问不到外部变量。改用箭头函数 fn() 可自动捕获。
  6. 引用参数造成意外副作用:调用方看不出函数会改原值,优先返回值。

小结

  • 参数与返回值都写类型,配合 declare(strict_types=1);。
  • ?type 表示可空,A|B 表示联合类型,void 表示无返回。
  • 可变参数用 ...,调用时也能用 ... 展开数组。
  • 函数不共享外部作用域:用参数传递,配置用常量。
  • 短回调优先箭头函数 fn()。

下一步