1.3 函数:可复用的作业班组 本节摘要:函数是把一段处理流程编成固定班组的手段:起个名字、定好入口参数与出口结果,之后随时点名调用。本节沿 1.2 的表单校验场景继续推进,讲参数传递、类型声明、作用域与匿名函数,最后把散落的校验代码整编成一个可复用的校验班组——它是第 2.1 节面向对象的前身。 重复的校验代码该往哪放 上一节结束时,报名表单的校验长长地摊在主流程里:姓名判空、年龄卡范围,一个页面这么写还行,十个页面都这么写,改一处就得追十处。服务端开发的朴素道理是:同一段逻辑出现第二次,就该编成班组(函数),主流程只负责点名。这就是函数存在的理由——不神秘,就是给代码块命名并复用。 第一行读法: 声明、名字 、括号里是参数清单、冒号后面 是返回值类型声明。类型声明不是装饰——1.
本节摘要:函数是把一段处理流程编成固定班组的手段:起个名字、定好入口参数与出口结果,之后随时点名调用。本节沿 1.2 的表单校验场景继续推进,讲参数传递、类型声明、作用域与匿名函数,最后把散落的校验代码整编成一个可复用的校验班组——它是第 2.1 节面向对象的前身。
上一节结束时,报名表单的校验长长地摊在主流程里:姓名判空、年龄卡范围,一个页面这么写还行,十个页面都这么写,改一处就得追十处。服务端开发的朴素道理是:同一段逻辑出现第二次,就该编成班组(函数),主流程只负责点名。这就是函数存在的理由——不神秘,就是给代码块命名并复用。
<?php // 最简单的班组:输入一个值,给一个结论 function isBlank(string $value): bool { return trim($value) === ''; } var_dump(isBlank(' ')); // bool(true) var_dump(isBlank('张三')); // bool(false)
第一行读法:function 声明、名字 isBlank、括号里是参数清单、冒号后面 bool 是返回值类型声明。类型声明不是装饰——1.2 节开过严格模式,参数类型不对会当场报错,班组边界由此变得清晰。
参数可以带默认值,调用时省略即取默认;PHP 8 还支持按名字传参,跳过中间的默认项直接指定目标参数:
<?php function ticket(int $from, int $to, string $seat = '二等座', bool $fast = true): string { return "{$from} 到 {$to} 的{$seat}" . ($fast ? '(直达)' : '(普快)'); } echo ticket(1001, 2002), "\n"; // 1001 到 2002 的二等座(直达) echo ticket(1001, 2002, fast: false), "\n"; // 1001 到 2002 的二等座(普快)
参数数量不定时用可变参数,PHP 把它们收进数组;返回值不止一种可能时用联合类型声明。这两件在处理请求参数时都常用:
<?php function sum(int ...$nums): int { return array_sum($nums); } echo sum(1, 2, 3), "\n"; // 6 function findUser(int|string $key): ?array { // ?array 表示"数组或 null" // 实际项目里这里查数据库,演示直接返回 null return null; } var_dump(findUser(7)); // NULL
作用域是新手最容易摔的一跤:函数内部默认看不见外部的变量。想在班组里引用外面的东西,要么作为参数传进来(首选,边界最清楚),要么用 static 让函数在多次调用间记住某个值(比如计数器):
<?php function nextId(): int { static $id = 0; // 只在首次调用时初始化,随后跨调用保留 return ++$id; } echo nextId(), nextId(), nextId(), "\n"; // 123
参数传入是默认选择的原因很直接:依赖都摆在签名上,谁调用、传什么,一眼可查;藏在函数体里的外部依赖,排查问题时得钻进去翻。
并非所有逻辑都值得起名字。PHP 支持匿名函数(闭包)与更短的箭头函数,两者最常出现在"把一段行为当参数传给别的函数"的场景里,比如数组加工:
<?php $fares = [120, 80, 240, 60]; $over100 = array_filter($fares, function (int $f): bool { return $f >= 100; }); print_r($over100); // [0 => 120, 2 => 240] $withTax = array_map(fn(int $f): int => (int) round($f * 1.1), $fares); print_r($withTax); // [132, 88, 264, 66]
两种写法的差别在捕获外部变量:匿名函数要用 use 显式搬进来,箭头函数自动捕获父作用域变量(按值)。演示对比:
<?php $rate = 0.1; $old = function (int $f) use ($rate): float { return $f * (1 + $rate); }; $new = fn(int $f): float => $f * (1 + $rate); echo $old(100), ' ', $new(100), "\n"; // 110 110,行为一致
选择建议就一句:表达式能一行写完用箭头函数,逻辑长了用命名函数。匿名函数体一旦超过几行,取名成正式班组反而更好找。
回到报名表单,把 1.2 节摊在主流程的校验整编成一组函数。输入输出都在签名上,主流程瘦成三行:
<?php declare(strict_types=1); function checkName(string $name): ?string { return trim($name) === '' ? '姓名不能为空' : null; } function checkAge(int $age): ?string { return ($age < 1 || $age > 120) ? '年龄不在合理范围' : null; } /** 跑一遍全部检查项,返回错误清单(空数组即通过) */ function validate(array $input): array { $checks = [ checkName((string) ($input['name'] ?? '')), checkAge((int) ($input['age'] ?? 0)), ]; return array_filter($checks, fn(?string $e): bool => $e !== null); } $errors = validate($_POST ?? []); if ($errors) { foreach ($errors as $e) { echo "退件:{$e}\n"; } exit(1); } echo "受理成功\n";
这个 validate 骨架的妙处在扩展性:以后加"邮箱格式""手机号格式",只需再写一个 check 函数、往数组里添一行,主流程纹丝不动。第 2.1 节把这类"同一批规则"升级成类的属性与方法时,你会看到今天的班组如何变成固定岗位。中途若想验证某条规则,单独调用对应的 check 函数即可,不必跑全流程。
⚠️ 函数里偷偷用外部变量:全局变量一旦进函数体,复用到别的页面就可能翻车,依赖一律从参数走。
⚠️ 返回值类型漂移:有时返回数组、有时返回 false,调用方写得提心吊胆;统一"返回数组(可能为空)或可空类型",调用方代码立刻清爽。
static 只留给计数这类极少数场景。下一节看数组:请求数据在 PHP 里几乎都以数组形态出现,它是行李清单,也是接下来所有批处理动作的对象。