用户自定义函数
概述
函数是将一段可重复使用的代码封装起来的核心机制。在 PHP 中,用户可以通过 function 关键字定义自己的函数,实现代码的逻辑复用、模块化和抽象。合理使用函数是编写清晰、可维护 PHP 程序的基础。
PHP 函数遵循以下核心规则:
- 函数名不区分大小写,但推荐统一使用驼峰命名(camelCase)
- 函数无需在调用前定义(PHP 会先扫描整个文件),但条件定义除外
- PHP 不支持函数重载(同名函数不能重复定义)
- 支持递归调用,但需注意栈溢出风险
PHP 版本说明
本文档以 PHP 8.1+ 为基准。函数的基本语法自 PHP 4 起保持稳定,类型声明自 PHP 7.0 起逐步增强,返回类型自 PHP 7.0 引入,never 返回类型自 PHP 8.1 引入。
基础概念
什么是函数
函数(Function)是一个被命名的、可重复执行的代码块。它接收输入(参数),经过处理后可能返回输出(返回值)。函数的主要作用包括:
- 代码复用:避免重复编写相同逻辑
- 模块化:将复杂问题分解为小模块
- 抽象:隐藏实现细节,只暴露接口
- 可测试性:独立的函数更容易进行单元测试
函数与语言结构
PHP 中"函数"和"语言结构"是不同的概念。echo、isset、empty、die 等是语言结构(language construct),不是函数,因此不能作为可变函数调用。
<?php
declare(strict_types=1);
// echo 是语言结构,不是函数
echo "Hello, World!\n";
// strlen 是真正的函数,可以用可变函数调用
$func = 'strlen';
echo $func('hello') . "\n"; // 输出: 5语法与代码
函数定义基本语法
使用 function 关键字定义函数,后面跟函数名、参数列表和函数体。
<?php
declare(strict_types=1);
// 最基本的函数定义
function sayHello(): void
{
echo "Hello, World!\n";
}
sayHello(); // 输出: Hello, World!
// 带参数的函数
function greet(string $name): string
{
return "Hello, {$name}!";
}
echo greet('Alice') . "\n"; // 输出: Hello, Alice!
// 带默认值的参数
function repeat(string $text, int $times = 2): string
{
return str_repeat($text, $times);
}
echo repeat('Hi ') . "\n"; // 输出: Hi Hi
echo repeat('Hi ', 3) . "\n"; // 输出: Hi Hi Hi命名规则
PHP 函数名遵循标识符命名规则,以字母或下划线开头,后面可以跟字母、数字和下划线。
<?php
declare(strict_types=1);
// 合法的函数名
function myFunction(): void {}
function my_function(): void {}
function _privateHelper(): void {}
function calculateTotal2(): void {}
// 函数名不区分大小写(但不推荐这样调用)
function MyFunction(): void
{
echo "Called\n";
}
myfunction(); // 正常调用
MYFUNCTION(); // 正常调用(但不推荐)
MyFunction(); // 推荐:与定义时保持一致注意事项
虽然 PHP 函数名不区分大小写,但为了代码可读性和团队协作,强烈推荐在定义和调用时保持一致的大小写。使用驼峰命名法(camelCase)是 PSR-12 推荐的风格。
函数定义顺序
PHP 在执行脚本之前会先扫描整个文件,因此函数可以在定义之前调用。但条件定义的函数只能在条件满足后才能调用。
<?php
declare(strict_types=1);
// 在定义之前调用(合法)
echo add(2, 3) . "\n"; // 输出: 5
function add(int $a, int $b): int
{
return $a + $b;
}
// 条件定义的函数
$useStrict = true;
if ($useStrict) {
function strictAdd(int $a, int $b): int
{
return $a + $b;
}
}
echo strictAdd(10, 20) . "\n"; // 输出: 30
// 以下调用会报错,因为条件不满足
// if (!$useStrict) {
// function anotherAdd(int $a, int $b): int { ... }
// }
// anotherAdd(1, 2); // Fatal error: Uncaught Error: Call to undefined function递归函数
递归函数是调用自身的函数。递归需要有明确的终止条件,否则会导致栈溢出。
<?php
declare(strict_types=1);
// 计算阶乘
function factorial(int $n): int
{
if ($n <= 1) {
return 1;
}
return $n * factorial($n - 1);
}
echo factorial(5) . "\n"; // 输出: 120
echo factorial(10) . "\n"; // 输出: 3628800
// 计算斐波那契数列(第 n 项)
function fibonacci(int $n): int
{
if ($n <= 0) {
throw new InvalidArgumentException('n must be positive');
}
if ($n === 1 || $n === 2) {
return 1;
}
return fibonacci($n - 1) + fibonacci($n - 2);
}
echo fibonacci(10) . "\n"; // 输出: 55
// 递归遍历目录(实用示例)
function listDirectory(string $dir, int $depth = 0): array
{
$result = [];
$items = scandir($dir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$path = $dir . DIRECTORY_SEPARATOR . $item;
$prefix = str_repeat(' ', $depth);
$result[] = $prefix . $item;
if (is_dir($path)) {
$result = array_merge($result, listDirectory($path, $depth + 1));
}
}
return $result;
}递归深度限制
PHP 默认没有硬性的递归深度限制,但受 memory_limit 和栈空间约束。对于深度可能很大的递归(如遍历深层嵌套数据),建议使用迭代方式或尾递归优化(PHP 本身不自动优化尾递归)。
详细说明
函数的组成部分
一个完整的 PHP 函数包含以下部分:
| 组成部分 | 说明 | 是否必需 |
|---|---|---|
function 关键字 | 声明函数的关键字 | 是 |
| 函数名 | 遵循标识符规则 | 是 |
| 参数列表 | 括号内的参数,可以为空 | 是(括号必需) |
| 返回类型声明 | 冒号后的类型(PHP 7.0+) | 否(推荐) |
| 函数体 | 花括号内的代码块 | 是 |
作用域规则
PHP 函数内部有自己的作用域,无法直接访问外部变量。需要使用 global 关键字、use 语言结构或参数传递来访问外部变量。
<?php
declare(strict_types=1);
$globalVar = 'I am global';
function accessGlobal(): void
{
// 无法直接访问 $globalVar
// echo $globalVar; // Undefined variable
// 使用 global 关键字(不推荐)
global $globalVar;
echo $globalVar . "\n"; // 输出: I am global
}
// 推荐方式:通过参数传递
function useParameter(string $value): void
{
echo $value . "\n";
}
useParameter($globalVar); // 输出: I am global最佳实践
避免使用 global 关键字,它会使函数产生隐式依赖,降低代码可测试性。推荐通过参数传递来注入外部值。
函数的返回值
使用 return 语句从函数返回值。如果函数没有 return 语句或 return 后没有值,则返回 null。
<?php
declare(strict_types=1);
// 无返回值(实际返回 null)
function noReturn(): void
{
echo "This function has no return statement\n";
}
$result = noReturn();
var_dump($result); // NULL
// 显式返回 null
function returnsNull(): ?string
{
return null;
}
// 返回多种类型的值(PHP 8.0+ 联合类型)
function flexibleReturn(int $type): int|string|array
{
return match ($type) {
1 => 42,
2 => 'hello',
3 => [1, 2, 3],
default => throw new InvalidArgumentException('Invalid type'),
};
}实战示例
格式化金额工具函数
<?php
declare(strict_types=1);
/**
* 格式化金额
*
* @param float|int $amount 金额数值
* @param string $currency 货币符号
* @param int $decimals 小数位数
* @return string 格式化后的金额字符串
*/
function formatCurrency(float|int $amount, string $currency = '¥', int $decimals = 2): string
{
$formatted = number_format((float) $amount, $decimals);
return $currency . $formatted;
}
echo formatCurrency(1234.5) . "\n"; // ¥1,234.50
echo formatCurrency(1234.5, '$') . "\n"; // $1,234.50
echo formatCurrency(1234, '€', 0) . "\n"; // €1,235数据验证函数组
<?php
declare(strict_types=1);
function validateEmail(string $email): bool
{
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
function validateAge(int $age): bool
{
return $age >= 0 && $age <= 150;
}
function validateRequired(array $data, array $fields): array
{
$errors = [];
foreach ($fields as $field) {
if (!isset($data[$field]) || $data[$field] === '') {
$errors[$field] = "The {$field} field is required";
}
}
return $errors;
}
// 使用示例
$data = ['name' => 'Alice', 'email' => 'alice@example.com'];
$errors = validateRequired($data, ['name', 'email', 'phone']);
if (!empty($errors)) {
print_r($errors);
// 输出: Array ( [phone] => The phone field is required )
}递归实现树形结构输出
<?php
declare(strict_types=1);
function printTree(array $nodes, int $parentId = 0, int $level = 0): void
{
foreach ($nodes as $node) {
if ($node['parent_id'] !== $parentId) {
continue;
}
$indent = str_repeat(' ', $level);
$prefix = $level > 0 ? '└─ ' : '';
echo "{$indent}{$prefix}{$node['name']}\n";
printTree($nodes, $node['id'], $level + 1);
}
}
$categories = [
['id' => 1, 'name' => '编程语言', 'parent_id' => 0],
['id' => 2, 'name' => 'PHP', 'parent_id' => 1],
['id' => 3, 'name' => 'Laravel', 'parent_id' => 2],
['id' => 4, 'name' => 'JavaScript', 'parent_id' => 1],
['id' => 5, 'name' => 'Vue.js', 'parent_id' => 4],
['id' => 6, 'name' => '数据库', 'parent_id' => 0],
['id' => 7, 'name' => 'MySQL', 'parent_id' => 6],
];
printTree($categories);
// 编程语言
// └─ PHP
// └─ Laravel
// └─ JavaScript
// └─ Vue.js
// 数据库
// └─ MySQL注意事项
常见陷阱
- 函数名冲突:PHP 不支持函数重载,定义两个同名函数会导致 Fatal Error
- 条件定义风险:条件定义的函数在不同条件下可能未定义,调用前需检查
- 递归无终止:递归函数必须有明确的终止条件,否则会导致内存溢出
- 大小写陷阱:虽然函数名不区分大小写,但类方法名在 PHP 8.0+ 区分大小写
<?php
declare(strict_types=1);
// 错误:不能重复定义函数
// function calculate(): int { return 1; }
// function calculate(): int { return 2; } // Fatal error: Cannot redeclare calculate()
// 安全的条件定义方式
if (!function_exists('calculate')) {
function calculate(): int
{
return 42;
}
}函数定义与 include/require
当函数通过 include 或 require 引入时,需特别注意多次引入导致的重复定义问题。推荐使用 require_once 或 include_once。
<?php
declare(strict_types=1);
// 安全引入函数文件
require_once __DIR__ . '/helpers.php';
require_once __DIR__ . '/utils.php';
// 或者在被引入的文件中保护函数定义
// helpers.php
if (!function_exists('helper_function')) {
function helperFunction(): void
{
// ...
}
}最佳实践
1. 单一职责原则
每个函数只做一件事,函数名应清晰描述其功能。
<?php
declare(strict_types=1);
// 不推荐:函数做了太多事情
function processUser(string $email, string $name): void
{
// 验证
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Invalid email');
}
// 保存到数据库
$db = new PDO('mysql:...');
$stmt = $db->prepare('INSERT INTO users ...');
$stmt->execute([$email, $name]);
// 发送邮件
mail($email, 'Welcome', 'Hello ' . $name);
}
// 推荐:拆分为多个单一职责函数
function validateUserEmail(string $email): bool
{
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
function saveUser(string $email, string $name): int
{
$db = new PDO('mysql:...');
$stmt = $db->prepare('INSERT INTO users (email, name) VALUES (?, ?)');
$stmt->execute([$email, $name]);
return (int) $stmt->lastInsertId();
}
function sendWelcomeEmail(string $email, string $name): bool
{
return mail($email, 'Welcome', "Hello {$name}");
}2. 始终声明类型
<?php
declare(strict_types=1);
// 不推荐:没有类型声明
function calculate($a, $b)
{
return $a + $b;
}
// 推荐:完整的类型声明
function calculateWithTypes(int $a, int $b): int
{
return $a + $b;
}3. 函数长度控制
- 每个函数建议不超过 20-30 行
- 超过一屏(约 50 行)应考虑拆分
- 嵌套层级不超过 3 层
4. 添加文档注释
<?php
declare(strict_types=1);
/**
* 计算两个日期之间的工作日天数
*
* @param string $startDate 开始日期(Y-m-d 格式)
* @param string $endDate 结束日期(Y-m-d 格式)
* @return int 工作日天数(不含周末)
* @throws InvalidArgumentException 当日期格式无效时抛出
*/
function getBusinessDays(string $startDate, string $endDate): int
{
$start = new DateTime($startDate);
$end = new DateTime($endDate);
if ($start > $end) {
throw new InvalidArgumentException('Start date must be before end date');
}
$interval = new DateInterval('P1D');
$period = new DatePeriod($start, $interval, $end);
$businessDays = 0;
foreach ($period as $day) {
$dayOfWeek = (int) $day->format('N');
if ($dayOfWeek < 6) { // 周一到周五
$businessDays++;
}
}
return $businessDays;
}