特殊函数
1. preg_match()函数
preg_match 函数简介(PHP 正则表达式匹配)
preg_match
是 PHP 中用于执行 正则表达式(Regular Expression) 匹配的核心函数,主要用于检查字符串是否匹配特定模式,并可以提取匹配的内容。
(1) 基本语法
preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0)
参数说明
参数 | 说明 |
---|---|
$pattern | 正则表达式模式(如 "/\d+/" 匹配数字) |
$subject | 要搜索的目标字符串 |
&$matches | (可选)存储匹配结果的数组 |
$flags | (可选)控制匹配行为(如 PREG_OFFSET_CAPTURE 返回匹配位置) |
$offset | (可选)从字符串的哪个位置开始搜索 |
返回值
1
:匹配成功(模式匹配到内容)。0
:未匹配到内容。false
:发生错误(如正则表达式语法错误)。
(2) 基本用法
① 检查字符串是否匹配
if (preg_match("/php/i", "PHP is great!")) {
echo "匹配成功!";
} else {
echo "未匹配!";
}
// 输出:匹配成功!(不区分大小写 `/i`)
② 提取匹配的内容
$text = "Date: 2023-10-05";
if (preg_match("/\d{4}-\d{2}-\d{2}/", $text, $matches)) {
echo "找到日期:" . $matches[0]; // 2023-10-05
}
$matches
数组结构:
$matches[0]
:完整匹配的字符串。$matches[1]
、$matches[2]
...:捕获组(括号()
内的部分)。
③ 使用捕获组提取特定部分
$email = "user@example.com";
if (preg_match("/^([a-z]+)@([a-z]+\.com)$/i", $email, $matches)) {
echo "用户名:" . $matches[1] . "<br>"; // user
echo "域名:" . $matches[2]; // example.com
}
④ 获取匹配位置(PREG_OFFSET_CAPTURE
)
preg_match("/\d+/", "ID: 12345", $matches, PREG_OFFSET_CAPTURE);
print_r($matches);
输出:
Array
(
[0] => Array
(
[0] => 12345 // 匹配的字符串
[1] => 4 // 匹配的起始位置(从 0 开始)
)
)
(3) 注意事项
- 正则定界符:通常使用
/
(如"/pattern/"
),但也可以用#
、~
等。 - 性能优化:避免过于复杂的正则,尤其是长字符串匹配时。
- 错误处理:检查返回值是否为
false
(如preg_match()
返回false
可能是正则语法错误)。 - 全局匹配:
preg_match
只匹配第一个结果,如果需要所有匹配,用preg_match_all
。
(4) 相关函数
preg_match_all
:匹配所有符合条件的结果。preg_replace
:正则替换字符串。preg_split
:用正则分割字符串。
(5)示例:匹配并提取所有数字
$text = "Age: 25, Height: 180cm";
preg_match_all("/\d+/", $text, $matches);
print_r($matches[0]); // Array ( [0] => 25 [1] => 180 )
(6)绕过方法
1.数组绕过,即传入的参数为数组
源代码如下:
效果:
成功绕过preg_match()
2.利用PCRE回溯次数限制绕过
源代码:
让回溯次数超过最大限制就可以使preg_match()
函数返回false
,从而绕过限制,中文的回溯次数在100万次就会崩溃,这个回溯保护使PHP为了防止关于正则表达式的DDOS
结果:
POC(概念验证):
成功绕过了preg_match()
脚本:
import requests
data = {"xdmtql": "sys nb" + "aaaaa" * 1000000}
res = requests.post('http://6fe942bf-0245-4683-a76c-ecbd54086f15.www.polarctf.com:8090/',
data=data, allow_redirects=False)
print(res.content)
# pre_match函数处理的字符长度有限,如果超过这个长度就会返回false也就是没有匹配到
# allow_redirects=False 表示禁止自动处理重定向。如果服务器返回重定向响应(如 302),requests 不会自动跟随重定向。
3.换行符绕过.
不会匹配换行符
源代码:
结果:
成功绕过
2. is_array()函数
is_array()
是 PHP 中的一个内置函数,用于检测给定的变量是否是一个数组(array)。
(1) 基本语法
bool is_array(mixed $var)
参数
$var
:要检查的变量(可以是任意类型)。
返回值
true
:如果$var
是数组。false
:如果$var
不是数组。
(2) 使用示例
① 检查变量是否为数组
$arr = [1, 2, 3];
if (is_array($arr)) {
echo "这是一个数组";
} else {
echo "这不是数组";
}
// 输出:这是一个数组
② 检查非数组变量
$str = "Hello";
var_dump(is_array($str)); // 输出:bool(false)
$num = 123;
var_dump(is_array($num)); // 输出:bool(false)
③ 用于函数参数验证
function processData($data) {
if (!is_array($data)) {
throw new InvalidArgumentException("参数必须是数组");
}
// 处理数组...
}
(3)注意事项
空数组也会返回
true
:php$emptyArr = []; var_dump(is_array($emptyArr)); // 输出:bool(true)
不适用于其他可迭代对象(如
ArrayObject
):php$obj = new ArrayObject([1, 2, 3]); var_dump(is_array($obj)); // 输出:bool(false)
如果需要检查,可以用
$var instanceof Traversable
。与
gettype()
的区别:php$arr = [1, 2, 3]; echo gettype($arr); // 输出:"array"
gettype()
返回变量类型的字符串表示,而is_array()
直接返回布尔值。
(4)相关函数
函数 | 说明 |
---|---|
is_string() | 检查变量是否为字符串 |
is_int() | 检查变量是否为整数 |
is_object() | 检查变量是否为对象 |
is_null() | 检查变量是否为 null |
empty() | 检查变量是否为空(包括空数组 [] ) |
(5)实际应用场景
① 表单数据处理
$userInput = $_POST['hobbies']; // 可能是一个数组(如多选框)
if (is_array($userInput)) {
foreach ($userInput as $hobby) {
// 处理每个选项
}
}
② API 响应验证
$apiResponse = json_decode($jsonStr, true); // 返回关联数组
if (is_array($apiResponse)) {
// 安全地遍历数据
}
③ 动态参数处理
function mergeConfigs($config) {
if (!is_array($config)) {
$config = [];
}
return array_merge($defaultConfig, $config);
}
(6)总结
is_array()
是 PHP 中用于类型安全的关键函数,尤其在处理动态数据时非常有用。- 它仅对纯数组返回
true
,不适用于ArrayObject
等实现了数组接口的对象。 - 在函数或方法中,使用
is_array()
可以避免因类型错误导致的运行时异常。
如果需要检查更复杂的结构(如“是否可遍历”),可以结合 is_iterable()
(PHP 7.1+)或 instanceof
使用。
3. strpos()函数
strpos()
是 PHP 中用于 查找字符串首次出现位置 的内置函数,区分大小写。如果找到子字符串,返回它的 数字索引位置(从 0 开始);如果未找到,返回 false
。
(1)基本语法
mixed strpos(string $haystack, string $needle, int $offset = 0)
参数说明
参数 | 描述 |
---|---|
$haystack | 要搜索的 主字符串 |
$needle | 要查找的 子字符串 |
$offset | 可选,从主字符串的哪个位置开始搜索(默认 0) |
返回值
int
:子字符串首次出现的索引位置(从 0 开始)。false
:如果未找到子字符串。
(2)基础用法示例
① 检查子字符串是否存在
$text = "Hello, world!";
$position = strpos($text, "world");
if ($position !== false) {
echo "找到 'world',位置在索引 $position"; // 输出:找到 'world',位置在索引 7
} else {
echo "未找到";
}
注意:必须用 !== false
判断,因为 0
也可能是有效位置(如 strpos("abc", "a")
返回 0
)。
② 使用 $offset
跳过部分内容
$text = "apple, banana, apple";
$position = strpos($text, "apple", 5); // 从索引 5 开始搜索
echo $position; // 输出:14(第二个 "apple" 的位置)
③ 区分大小写
$text = "PHP is Fun!";
$position = strpos($text, "fun"); // 返回 false(大小写不匹配)
(3)注意事项
① 严格判断返回值
// 错误写法(如果子字符串在开头,0 会被视为 false):
if (strpos($text, "abc")) { ... }
// 正确写法:
if (strpos($text, "abc") !== false) { ... }
② 多字节字符(如中文)
strpos()
对多字节字符(如 UTF-8)可能不准确,建议用 mb_strpos()
:
$text = "你好,世界!";
$position = mb_strpos($text, "世界"); // 返回 3(每个中文字符算 1 个位置)
③ $needle
为空字符串
$result = strpos("abc", ""); // 返回 0(PHP 8.0+ 会抛出错误)
(4)实际应用场景
① 检查关键词是否存在
$content = "This is a sample text.";
if (strpos($content, "sample") !== false) {
echo "内容包含关键词 'sample'";
}
② 提取子字符串后的内容
$url = "https://example.com/page";
if (strpos($url, "://") !== false) {
$protocol = substr($url, 0, strpos($url, "://"));
echo "协议:$protocol"; // 输出:https
}
③ 替换首次匹配项
$text = "cat, dog, cat";
$pos = strpos($text, "cat");
if ($pos !== false) {
$text = substr_replace($text, "lion", $pos, strlen("cat"));
}
echo $text; // 输出:lion, dog, cat
(5)相关函数对比
函数 | 描述 | 区别 |
---|---|---|
stripos() | 不区分大小写的查找 | 大小写不敏感 |
strrpos() | 查找子字符串最后一次出现的位置 | 反向搜索 |
mb_strpos() | 多字节安全的查找 | 支持中文等字符 |
str_contains() (PHP 8+) | 检查是否包含子字符串 | 直接返回 bool |
(6)总结
strpos()
是 区分大小写 的字符串搜索函数,返回 位置索引 或false
。- 关键点:始终用
!== false
判断结果。 - 多字节字符用
mb_strpos()
,不区分大小写用stripos()
。 - PHP 8+ 推荐
str_contains()
简化存在性检查。
4. include()函数
在 PHP 中,include
是一个用于将外部文件的内容插入到当前脚本中的语言构造。它通常用于代码复用和模块化开发,让你能够在多个 PHP 文件中共享代码,如函数、类、配置文件或 HTML 结构等。
(1)基本语法
include 'file.php';
工作原理
- 当 PHP 执行
include
时,它会读取指定的文件,并将文件的内容插入到调用include
的位置。 - 如果文件无法找到或包含失败,PHP 会发出一个 警告(Warning),但脚本会继续执行。
示例
// header.php
echo "<h1>欢迎访问我的网站</h1>";
// main.php
include 'header.php'; // 包含 header.php 文件
echo "<p>这是页面的主要内容</p>";
- 输出:
<h1>欢迎访问我的网站</h1>
<p>这是页面的主要内容</p>
(2)include
与 require
的区别
include
:如果文件无法找到,PHP 会发出警告(Warning),但脚本会继续执行。require
:如果文件无法找到,PHP 会发出致命错误(Fatal Error),并停止执行脚本。
(3)动态包含文件
include
也可以通过变量指定文件路径,这允许你根据条件动态选择要包含的文件。
$file = 'header.php';
include $file; // 动态包含文件
(5)安全性注意
- 使用
include
时,确保文件路径正确,以防止路径泄漏或恶意文件包含。 - 为了避免目录遍历攻击,可以使用绝对路径或进行路径验证。
5. in_array()函数
in_array()
是 PHP 中一个常用的数组函数,用于检查某个值是否存在于数组中。
(1)基本语法
bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
参数说明
$needle
:要搜索的值$haystack
:要搜索的数组$strict
(可选):是否使用严格模式(类型检查),默认为 FALSE
返回值
- 如果找到值则返回
true
- 如果未找到则返回
false
(2)使用示例
基本用法
$fruits = ['apple', 'banana', 'orange'];
if (in_array('apple', $fruits)) {
echo '找到了苹果!';
}
严格模式
$numbers = [1, 2, 3, '4'];
// 非严格模式 - 会匹配
var_dump(in_array('4', $numbers)); // 输出: bool(true)
// 严格模式 - 不会匹配
var_dump(in_array('4', $numbers, true)); // 输出: bool(false)
(3)实际应用
$allowed_extensions = ['jpg', 'png', 'gif'];
$file_extension = 'pdf';
if (!in_array($file_extension, $allowed_extensions)) {
echo '不允许的文件类型!';
}
(4)注意事项
- 在大型数组上使用
in_array()
可能会影响性能,因为它是线性搜索 - 对于键值对数组,如果只需要检查键是否存在,使用
array_key_exists()
更高效 - 严格模式会同时比较值和类型
- 对于对象比较,严格模式下会比较是否为同一个实例
(5)替代方案
如果需要进行多次存在性检查,可以考虑先将数组转换为关联数组(使用值作为键),然后使用 isset()
或 array_key_exists()
,这样效率更高。
$values = ['a', 'b', 'c'];
$flipped = array_flip($values);
if (isset($flipped['a'])) {
echo '存在';
}
6. call_user_func_array() 函数
call_user_func_array()
是 PHP 中一个非常有用的函数,它允许你动态调用一个回调函数,并将参数作为数组传递。
(1)基本语法
mixed call_user_func_array ( callable $callback , array $param_arr )
参数说明
$callback
:要调用的回调函数,可以是:- 函数名的字符串
- 包含方法名和对象的数组
[$object, 'methodName']
- 包含类名和静态方法名的数组
['ClassName', 'staticMethod']
- 匿名函数(闭包)
$param_arr
:要传递给回调函数的参数数组,数组中的元素会作为单独的参数按顺序传递
返回值
返回回调函数的执行结果。
(2)使用示例
调用普通函数
function sum($a, $b) {
return $a + $b;
}
$result = call_user_func_array('sum', [5, 3]);
echo $result; // 输出 8
调用对象方法
class Calculator {
public function multiply($x, $y) {
return $x * $y;
}
}
$calc = new Calculator();
$result = call_user_func_array([$calc, 'multiply'], [4, 5]);
echo $result; // 输出 20
调用静态方法
class MathUtils {
public static function subtract($a, $b) {
return $a - $b;
}
}
$result = call_user_func_array(['MathUtils', 'subtract'], [10, 4]);
echo $result; // 输出 6
使用匿名函数
$callback = function($name, $greeting) {
return "$greeting, $name!";
};
$message = call_user_func_array($callback, ['World', 'Hello']);
echo $message; // 输出 "Hello, World!"
(3)实际应用场景
动态调用函数:当函数名存储在变量中时
php$functionName = 'doSomething'; call_user_func_array($functionName, $args);
事件处理系统:将事件分发给不同的处理器
php$eventHandlers = [ 'login' => [$userService, 'handleLogin'], 'logout' => [$userService, 'handleLogout'] ]; $event = 'login'; $data = ['user_id' => 123]; if (isset($eventHandlers[$event])) { call_user_func_array($eventHandlers[$event], [$data]); }
中间件或管道模式:将请求通过一系列处理器传递
php$middlewares = [$middleware1, $middleware2, $middleware3]; $request = new Request(); foreach ($middlewares as $middleware) { $request = call_user_func_array($middleware, [$request]); }
(4)注意事项
参数数组中的元素会按顺序传递给回调函数
如果参数数量不匹配,可能会导致错误
在 PHP 5.6+ 中,可以使用
...
参数解包操作符作为替代:php$callback(...$param_arr);
性能考虑:
call_user_func_array()
比直接调用函数稍慢
(5)与类似函数的比较
call_user_func()
:类似,但参数是单独传递而不是数组forward_static_call_array()
:用于静态方法的调用,并保持后期静态绑定
call_user_func_array()
提供了极大的灵活性,特别适合需要动态决定调用哪个函数或方法的场景。
7. intval()函数
intval()
是 PHP 中用于获取变量整数值的函数,可以将各种类型的变量转换为整数。
(1)基本语法
int intval ( mixed $var [, int $base = 10 ] )
参数说明
$var
:要转换为整数的变量$base
(可选):转换的进制基数(仅当$var
是字符串时有效)- 取值范围:2-36
- 默认值:10
返回值
返回 $var
的整数值,转换失败时返回 0。
(2)使用示例
基本转换
echo intval(42); // 输出: 42
echo intval(4.2); // 输出: 4
echo intval('42'); // 输出: 42
echo intval('+42'); // 输出: 42
echo intval('-42'); // 输出: -42
echo intval(042); // 输出: 34 (八进制转十进制)
echo intval('042'); // 输出: 42
echo intval(1e10); // 输出: 10000000000
echo intval('1e10'); // 输出: 1
布尔值转换
echo intval(true); // 输出: 1
echo intval(false); // 输出: 0
NULL 转换
echo intval(null); // 输出: 0
数组转换
echo intval([]); // 输出: 0
echo intval([1, 2, 3]); // 输出: 1 (PHP 7+)
对象转换
class Test {}
$obj = new Test();
echo intval($obj); // 输出: 1 (PHP 5+)
使用不同基数
echo intval('1a', 16); // 输出: 26 (16进制)
echo intval('1011', 2); // 输出: 11 (二进制)
echo intval('ff', 16); // 输出: 255
echo intval('077', 8); // 输出: 63
(3)实际应用场景
表单数据处理:
php$age = intval($_POST['age']);
数据库查询参数处理:
php$id = intval($_GET['id']); $sql = "SELECT * FROM users WHERE id = $id";
类型安全比较:
phpif (intval($input) === 1) { // 严格比较 }
进制转换:
php$hexValue = 'a1'; $decimal = intval($hexValue, 16); // 161
(4)注意事项
浮点数转换:会直接截断小数部分(不是四舍五入)
phpecho intval(4.9); // 输出4
字符串转换规则:
- 从字符串开头解析数字,遇到非数字字符停止
phpecho intval('42abc'); // 输出42 echo intval('abc42'); // 输出0
大数处理:
- 32位系统上最大值为2147483647
- 64位系统上最大值为9223372036854775807
- 超出会返回这些最大值
PHP版本差异:
- PHP 5: 空数组返回0,非空数组返回1
- PHP 7+: 所有数组都返回0
替代方案:
- 类型转换:(int)$var
- 类型强制:(integer)$var
- 但
intval()
可以指定进制
(5)最佳实践
对于用户输入,建议先过滤再转换:
php$id = intval(filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT));
对于需要四舍五入的情况,先用
round()
或floor()
:php$value = intval(round(4.6)); // 5
重要数据应考虑使用
filter_var()
进行更严格的验证:php$options = ['options' => ['min_range' => 1]]; $id = filter_var($_GET['id'], FILTER_VALIDATE_INT, $options);
intval()
是处理整数转换的便捷工具,但在安全敏感场景中,建议结合其他过滤函数使用。
8. substr()函数
substr()
是 PHP 中用于截取字符串的函数,可以从字符串中返回指定的一部分。
(1)基本语法
string substr ( string $string , int $start [, int $length ] )
参数说明
$string
:输入的字符串$start
:开始截取的位置- 正数:从字符串开头第
$start
个字符开始(0为第一个字符) - 负数:从字符串末尾倒数第
$start
个字符开始
- 正数:从字符串开头第
$length
(可选):截取的长度- 正数:返回最多
$length
个字符 - 负数:从字符串末尾忽略
$length
个字符 - 省略:截取到字符串末尾
- 正数:返回最多
返回值
返回字符串的截取部分,失败时返回 false
(PHP 8.0+ 返回空字符串)。
(2)使用示例
基本用法
$str = "Hello, World!";
echo substr($str, 0, 5); // 输出: Hello
echo substr($str, 7); // 输出: World!
echo substr($str, -6); // 输出: World!
echo substr($str, -6, 3); // 输出: Wor
echo substr($str, 7, -1); // 输出: World
负数位置示例
$str = "abcdef";
echo substr($str, -3, 1); // 输出: d
echo substr($str, -3, -1); // 输出: de
echo substr($str, 2, -1); // 输出: cde
(3)实际应用场景
截取文件扩展名:
php$filename = "example.jpg"; $ext = substr($filename, strrpos($filename, '.') + 1); echo $ext; // 输出: jpg
显示文章摘要:
php$content = "这是一段很长的文章内容..."; $summary = substr($content, 0, 100) . '...';
处理手机号显示:
php$phone = "13800138000"; $display = substr($phone, 0, 3) . '****' . substr($phone, -4); echo $display; // 输出: 138****8000
提取字符串中间部分:
php$str = "Name: John; Age: 30;"; $age = substr($str, strpos($str, "Age: ") + 5, 2); echo $age; // 输出: 30
(4)注意事项
多字节字符问题:
substr()
不识别多字节字符(如中文)- 对于UTF-8等编码,应使用
mb_substr()
php$chinese = "你好世界"; echo substr($chinese, 0, 2); // 可能输出乱码 echo mb_substr($chinese, 0, 2); // 正确输出: 你好
PHP版本差异:
- PHP 7.0-:
substr('abc', 5)
返回false
- PHP 8.0+:
substr('abc', 5)
返回空字符串""
- PHP 7.0-:
边界情况:
phpecho substr("abc", 1, 10); // 输出: bc(不会填充空字符) echo substr("abc", -10); // 输出: abc(自动调整起始位置)
性能考虑:
- 对于大字符串操作,
substr()
效率较高 - 但频繁调用可能影响性能
- 对于大字符串操作,
(5)替代方案
mb_substr():处理多字节字符
phpecho mb_substr("こんにちは", 0, 2); // 输出: こん
字符串访问语法(PHP 7.1+):
php$str = "Hello"; echo $str[1]; // 输出: e echo $str[-1]; // 输出: o
正则表达式:更复杂的提取需求
phppreg_match('/\d+/', 'abc123def', $matches); echo $matches[0]; // 输出: 123
(6)最佳实践
对于UTF-8等编码,始终使用
mb_substr()
检查返回值有效性:
php$result = substr($str, $start, $length); if ($result !== false) { // 处理有效结果 }
结合其他字符串函数使用:
php// 安全截取并添加省略号 function safeSubstr($str, $len) { if (mb_strlen($str) > $len) { return mb_substr($str, 0, $len) . '...'; } return $str; }
9. 数据类型判断函数
PHP 提供了一系列用于判断变量数据类型的函数,这些函数在变量处理和类型检查中非常有用。
基本类型判断函数
(1) is_int() / is_integer() / is_long()
判断是否为整数
is_int(42); // true
is_int("42"); // false
is_int(4.2); // false
(2) is_float() / is_double()
判断是否为浮点数
is_float(3.14); // true
is_float(42); // false
is_float("3.14"); // false
(3) is_string()
判断是否为字符串
is_string("hello"); // true
is_string(123); // false
(4) is_bool()
判断是否为布尔值
is_bool(true); // true
is_bool(false); // true
is_bool(0); // false
(5) is_array()
判断是否为数组
is_array([]); // true
is_array([1, 2, 3]); // true
is_array("array"); // false
(6) is_object()
判断是否为对象
is_object(new stdClass()); // true
is_object("object"); // false
(7) is_null()
判断是否为null
is_null(null); // true
is_null(0); // false
is_null("null"); // false
(8) is_resource()
判断是否为资源类型
$file = fopen('test.txt', 'r');
is_resource($file); // true
fclose($file);
is_resource($file); // false
复合类型判断函数
(9) is_scalar()
判断是否为标量类型(int, float, string, bool)
is_scalar(123); // true
is_scalar("abc"); // true
is_scalar([]); // false
(10) is_callable()
判断是否可调用(函数名、方法、闭包)
is_callable('strlen'); // true
is_callable(function() {}); // true
is_callable([$obj, 'method']);// 如果方法存在则为true
is_callable('no_function'); // false
(11) is_iterable() (PHP 7.1+)
判断是否可迭代(数组或实现Traversable接口的对象)
is_iterable([]); // true
is_iterable(new ArrayObject); // true
is_iterable("string"); // false
特殊类型判断函数
(12) is_numeric()
判断是否为数字或数字字符串
is_numeric(123); // true
is_numeric("123"); // true
is_numeric("12.3"); // true
is_numeric("12a"); // false
(13) is_countable() (PHP 7.3+)
判断是否可计数(数组或实现Countable接口的对象)
is_countable([]); // true
is_countable(new ArrayObject); // true
is_countable("string"); // false
(14) is_finite() / is_infinite() / is_nan()
浮点数特殊判断
is_finite(1.0); // true
is_infinite(log(0)); // true
is_nan(acos(2)); // true
(15)最佳实践
类型安全比较:
phpif (is_int($value) && $value === 42) { // 严格类型检查 }
处理用户输入:
php$age = $_POST['age']; if (!is_numeric($age)) { die("年龄必须是数字"); } $age = (int)$age;
方法参数验证:
phppublic function process(array $data) { if (!is_array($data)) { throw new InvalidArgumentException("参数必须是数组"); } // ... }
使用类型提示替代(PHP 7.0+):
phppublic function calculate(int $a, float $b): float { return $a * $b; }
多类型检查:
phpif (is_string($value) || is_numeric($value)) { // 处理字符串或数字 }
(16)注意事项
- 这些函数返回布尔值(true/false)
- 大多数函数有对应的"is not"形式,如
!is_string()
- 对于对象,还可以使用
instanceof
检查特定类 - PHP 8.0+ 引入了更严格的类型系统,建议尽可能使用类型声明
10. count()函数
count()
是 PHP 中用于计算数组或对象中元素数量的核心函数。
(1)基本语法
int count ( mixed $array_or_countable [, int $mode = COUNT_NORMAL ] )
参数说明
$array_or_countable
:要计数的数组或实现Countable
接口的对象$mode
(可选):计数模式COUNT_NORMAL
(默认):常规计数,不递归统计多维数组COUNT_RECURSIVE
:递归统计多维数组的所有元素
返回值
返回元素的数量:
- 对于非数组且非可计数对象,返回 1(PHP 7.2+ 返回 0)
- 对于未初始化的变量,返回 0(PHP 7.2+)
- 对于数组或可计数对象,返回元素个数
(2)使用示例
基本用法
$fruits = ['apple', 'banana', 'orange'];
echo count($fruits); // 输出: 3
$empty = [];
echo count($empty); // 输出: 0
多维数组计数
$food = [
'fruits' => ['apple', 'banana', 'orange'],
'veggies' => ['carrot', 'cucumber']
];
// 常规模式
echo count($food); // 输出: 2(只统计第一维)
// 递归模式
echo count($food, COUNT_RECURSIVE); // 输出: 6(2+3+1)
对象计数
class MyCountable implements Countable {
public function count() {
return 5;
}
}
$obj = new MyCountable();
echo count($obj); // 输出: 5
特殊值处理
echo count(null); // 输出: 0(PHP 7.2+)
echo count(false); // 输出: 1(PHP 7.2+ 返回 0)
echo count("string"); // 输出: 1(PHP 7.2+ 返回 0)
(3)实际应用场景
检查数组是否为空
phpif (count($array) > 0) { // 数组不为空 }
遍历前检查
php$users = getUsers(); if (count($users)) { foreach ($users as $user) { // 处理用户 } }
统计多维数组深度
phpfunction array_depth($array) { $max_depth = 1; foreach ($array as $value) { if (is_array($value)) { $depth = array_depth($value) + 1; $max_depth = max($max_depth, $depth); } } return $max_depth; }
批量处理数据时分批
php$batchSize = 100; $totalItems = count($items); $batches = ceil($totalItems / $batchSize);
(4)注意事项
PHP版本差异
- PHP 7.2 之前:对非数组和非可计数对象返回 1
- PHP 7.2+:对非数组和非可计数对象返回 0
性能考虑
count()
对数组的时间复杂度是 O(1)- 递归计数大数组可能影响性能
替代方案
空数组检查:
empty()
函数更快phpif (!empty($array)) { ... }
数组大小:
sizeof()
是count()
的别名
最佳实践
优先使用类型提示(PHP 7.0+)
phpfunction processItems(array $items) { // 确保参数是数组 }
对可能为null的值先检查
php$count = $items ? count($items) : 0;
与empty()的区别
php$a = []; count($a); // 0 empty($a); // true $b = [0]; count($b); // 1 empty($b); // false
11. preg_replace()函数
preg_replace()
是 PHP 中一个强大的正则表达式替换函数,用于执行基于正则表达式的搜索和替换操作。
(1)基本语法
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
参数说明
$pattern
- 要搜索的模式,可以是字符串或字符串数组- 可以是一个正则表达式字符串,如
'/pattern/'
- 也可以是一个包含多个正则表达式的数组
- 可以是一个正则表达式字符串,如
$replacement
- 用于替换的字符串或字符串数组- 可以包含反向引用(如
\\1
或$1
) - 如果是数组,则与
$pattern
数组一一对应
- 可以包含反向引用(如
$subject
- 要进行搜索和替换的字符串或字符串数组$limit
(可选) - 每个模式在每个$subject
上进行替换的最大次数,默认为 -1(无限制)&$count
(可选) - 如果指定,将被填充为完成的替换次数
返回值
- 如果
$subject
是数组,则返回数组 - 其他情况返回字符串
- 发生错误时返回
NULL
(2)使用示例
基本替换
$string = "April 15, 2023";
$pattern = '/(\w+) (\d+), (\d+)/i';
$replacement = '$2 $1 $3';
echo preg_replace($pattern, $replacement, $string);
// 输出: 15 April 2023
使用数组模式
$patterns = array('/quick/', '/brown/', '/fox/');
$replacements = array('slow', 'black', 'bear');
$string = 'The quick brown fox jumps over the lazy dog.';
echo preg_replace($patterns, $replacements, $string);
// 输出: The slow black bear jumps over the lazy dog.
移除空白字符
$text = "This is some text \t with \n whitespace.";
$cleaned = preg_replace('/\s+/', ' ', $text);
echo $cleaned;
// 输出: This is some text with whitespace.
限制替换次数
$string = "a a a a a";
echo preg_replace('/a/', 'b', $string, 3);
// 输出: b b b a a
(3)注意事项
- 正则表达式模式通常需要分隔符,如
/pattern/
- 使用
preg_quote()
可以转义正则表达式中的特殊字符 - 对于简单字符串替换,
str_replace()
效率更高 - 反向引用可以使用
\\n
或$n
形式 - 替换字符串中的
$
需要转义为\$
(4)性能考虑
- 复杂的正则表达式可能影响性能
- 对于大量数据处理,考虑使用
preg_replace_callback()
preg_replace()
是处理复杂文本替换的强大工具,特别适合需要模式匹配的场景。
12. preg_match_all()函数
preg_match_all
是 PHP 中一个强大的正则表达式匹配函数,用于执行全局正则表达式匹配。
(1)基本语法
int preg_match_all(
string $pattern,
string $subject,
array &$matches = null,
int $flags = 0,
int $offset = 0
)
参数说明
$pattern
- 要搜索的正则表达式模式$subject
- 要搜索的输入字符串$matches
- 用于存储匹配结果的数组(引用传递)$flags
- 控制匹配结果的排列方式(可选)PREG_PATTERN_ORDER
(默认) - 按模式分组PREG_SET_ORDER
- 按匹配分组PREG_OFFSET_CAPTURE
- 同时返回匹配位置的偏移量
$offset
- 从目标字符串的哪个位置开始搜索(可选)
返回值
- 返回完整匹配的次数(可能是0)
- 如果发生错误,返回
false
(2)使用示例
示例1:基本用法
$subject = "a1b2c3d4";
preg_match_all('/\d/', $subject, $matches);
print_r($matches);
输出:
Array
(
[0] => Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
)
)
示例2:使用命名捕获组
$html = '<p>段落1</p><p>段落2</p>';
preg_match_all('/<p>(?<content>.*?)<\/p>/', $html, $matches);
print_r($matches['content']);
输出:
Array
(
[0] => 段落1
[1] => 段落2
)
示例3:使用 PREG_SET_ORDER 标志
$subject = "name: John, age: 30; name: Jane, age: 25";
preg_match_all('/name: (\w+), age: (\d+)/', $subject, $matches, PREG_SET_ORDER);
print_r($matches);
输出:
Array
(
[0] => Array
(
[0] => name: John, age: 30
[1] => John
[2] => 30
)
[1] => Array
(
[0] => name: Jane, age: 25
[1] => Jane
[2] => 25
)
)
(3)注意事项
- 比
preg_match
更强大,可以获取所有匹配而不仅是第一个 - 对于大型字符串,可能消耗较多内存
- 正则表达式应使用分隔符(如
/pattern/
) - 如果只需要检查是否匹配而不需要所有结果,
preg_match
效率更高
preg_match_all
是处理复杂文本匹配和提取的强大工具,特别适合需要从字符串中提取多个匹配项的场景。
13. exec()函数
exec
是一个用于执行外部程序的函数,在多种编程环境中存在,包括 PHP 和 Node.js,但行为有所不同。下面分别介绍:
(1)PHP 中的 exec()
基本语法
string exec(string $command [, array &$output [, int &$return_var]])
参数说明
$command
: 要执行的 shell 命令&$output
(可选): 数组,用于存储命令的每一行输出&$return_var
(可选): 存储命令的返回值(通常 0 表示成功)
返回值
- 返回命令执行的 最后一行输出(如果要获取全部输出,需使用
$output
参数)
示例
// 执行简单的 shell 命令
exec('ls -l', $output, $return_var);
// 打印所有输出行
print_r($output);
// 检查是否执行成功
if ($return_var === 0) {
echo "命令执行成功";
} else {
echo "命令执行失败";
}
安全注意事项
- 避免直接将用户输入拼接到命令中(有 命令注入 风险)
- 建议使用
escapeshellarg()
或escapeshellcmd()
对参数进行转义
(2) Node.js 中的 exec()
在 Node.js 中,exec
是 child_process
模块的一个函数,用于执行 shell 命令。
基本语法
const { exec } = require('child_process');
exec(command [, options], callback);
参数说明
command
: 要执行的 shell 命令options
(可选): 配置对象(如cwd
,env
,timeout
等)callback
: 执行完成后的回调函数,格式:(error, stdout, stderr) => { ... }
返回值
- 返回一个
ChildProcess
实例(可用于控制进程)
示例
const { exec } = require('child_process');
exec('ls -l', (error, stdout, stderr) => {
if (error) {
console.error(`执行错误: ${error}`);
return;
}
console.log(`输出:\n${stdout}`);
if (stderr) {
console.error(`错误输出:\n${stderr}`);
}
});
安全注意事项
- 同样存在 命令注入 风险,避免直接拼接用户输入
- 推荐使用
execFile
或spawn
替代,安全性更高
主要区别
特性 | PHP exec() | Node.js exec() |
---|---|---|
返回值 | 最后一行输出 | 通过回调返回全部输出 (stdout ) |
输出获取 | 需传递 &$output 数组 | 直接在回调中接收 stdout |
异步/同步 | 同步执行 | 异步执行 |
错误处理 | 通过 $return_var 判断 | 通过回调的 error 参数处理 |
常见用途
- 调用系统命令(如压缩文件、处理图片)
- 执行脚本(Python、Bash 等)
- 与外部程序交互(如 Git、FFmpeg)
- 自动化任务(备份、部署)
替代方案
- PHP: 考虑
shell_exec()
、passthru()
或proc_open()
- Node.js: 更安全的
execFile()
或spawn()
14. str_replace()函数
str_replace
是 PHP 中一个常用的字符串替换函数,用于在字符串中查找并替换指定的内容。它支持 简单替换、数组批量替换 和 多对多替换,功能强大且灵活。
(1)基本语法
str_replace($search, $replace, $subject, [&$count])
$search
:要查找的字符串或字符串数组(旧值)。$replace
:替换成的字符串或字符串数组(新值)。$subject
:被搜索的原始字符串或字符串数组。$count
(可选):如果提供,函数会返回替换发生的次数(引用传递)。
返回值:替换后的字符串或数组。
(2) 使用示例
简单替换(单个字符串)
$text = "Hello World!";
$newText = str_replace("World", "PHP", $text);
echo $newText; // 输出:Hello PHP!
数组批量替换(多对一)
$text = "apple, banana, cherry";
$fruits = ["apple", "banana", "cherry"];
$newText = str_replace($fruits, "fruit", $text);
echo $newText; // 输出:fruit, fruit, fruit
多对多替换(数组对数组)
$text = "I like cats and dogs.";
$search = ["cats", "dogs"];
$replace = ["dogs", "birds"];
$newText = str_replace($search, $replace, $text);
echo $newText; // 输出:I like dogs and birds.
统计替换次数
$text = "foo bar foo baz foo";
$count = 0;
$newText = str_replace("foo", "FOO", $text, $count);
echo $newText; // 输出:FOO bar FOO baz FOO
echo "替换次数:" . $count; // 输出:3
(3)特点与注意事项
区分大小写:
str_replace
是 大小写敏感 的,如果需要不区分大小写,使用str_ireplace
。- php
$text = "Hello World!"; $newText = str_ireplace("world", "PHP", $text); // 匹配 "World"(不区分大小写) echo $newText; // 输出:Hello PHP!
数组替换规则:
- 如果
$search
和$replace
都是数组,则 按索引一一对应替换。 - 如果
$replace
比$search
短,多出的$search
会用空字符串""
替换。
- 如果
递归替换:
str_replace
不会递归替换(即不会对替换后的内容再次替换)。- php
$text = "a b c"; $newText = str_replace(["a", "b"], ["b", "c"], $text); echo $newText; // 输出:b c c(不会变成 c c c)
性能优化:
如果替换大量数据,
strtr()
可能比str_replace
更快:php$trans = ["hello" => "hi", "world" => "earth"]; $text = "hello world"; echo strtr($text, $trans); // 输出:hi earth
(4)常见应用场景
模板变量替换(如邮件模板):
php$template = "Hello {name}, your code is {code}"; $data = ["{name}" => "Alice", "{code}" => "1234"]; $message = str_replace(array_keys($data), array_values($data), $template); echo $message; // 输出:Hello Alice, your code is 1234
敏感词过滤:
php$text = "This is a bad word and another bad word."; $badWords = ["bad", "another"]; $filtered = str_replace($badWords, "***", $text); echo $filtered; // 输出:This is a *** word and *** *** word.
URL 或文件名处理:
php$filename = "image name with spaces.jpg"; $safeName = str_replace(" ", "-", $filename); echo $safeName; // 输出:image-name-with-spaces.jpg
(4)与 preg_replace
的区别
函数 | 匹配方式 | 适用场景 |
---|---|---|
str_replace | 固定字符串 | 简单替换,性能更高 |
preg_replace | 正则表达式 | 复杂模式匹配(如 /a.*b/ ) |
// 使用 preg_replace 替换所有数字
$text = "abc123def456";
$newText = preg_replace("/\d+/", "X", $text);
echo $newText; // 输出:abcXdefX
(5)总结
str_replace
是 PHP 中最常用的字符串替换函数,适合 固定字符串替换。- 支持 单次替换、批量替换、多对多替换,但不支持正则。
- 如果需要 不区分大小写 或 正则匹配,改用
str_ireplace
或preg_replace
。
适用于模板渲染、敏感词过滤、数据清洗等场景!
15. trim()函数
trim()
是 PHP 中一个常用的字符串处理函数,用于去除字符串首尾的空白字符(或其他指定字符)。
(1)基本语法
trim(string $str, string $character_mask = " \t\n\r\0\x0B") : string
参数说明
$str
: 要处理的输入字符串$character_mask
(可选): 指定要删除的字符列表,默认删除以下空白字符:- 普通空格 (
)
- 制表符 (
\t
) - 换行符 (
\n
) - 回车符 (
\r
) - 空字节 (
\0
) - 垂直制表符 (
\x0B
)
- 普通空格 (
返回值
返回去除首尾指定字符后的字符串
(2)使用示例
基本用法(去除空格)
$text = " Hello World! ";
echo trim($text); // 输出: "Hello World!"
去除特定字符
$text = "Hello World!";
echo trim($text, "Hed!"); // 输出: "llo Worl"
// 去除了首尾的 H、e、d、! 字符
处理用户输入
$username = trim($_POST['username']); // 去除用户输入首尾空格
与文件上传结合使用
$file_name = trim($_FILES['upload_file']['name']);
这段代码的作用是去除上传文件名首尾的空白字符,防止因意外空格导致的问题。
(3)相关函数
ltrim()
- 只去除字符串开头的空白字符(或其他字符)rtrim()
(或chop()
) - 只去除字符串末尾的空白字符(或其他字符)
(4)注意事项
trim()
不会修改字符串中间的空白字符- 对于多字节字符(如中文),可能需要使用
mb_trim()
替代 - 在处理文件路径时,过度使用
trim()
可能导致路径问题
16. str_ireplace()函数
str_ireplace()
是 PHP 中用于字符串替换的函数,不区分大小写地执行查找替换操作。
(1)基本语法
str_ireplace(
mixed $search,
mixed $replace,
mixed $subject,
int &$count = null
): mixed
参数说明
参数 | 说明 |
---|---|
$search | 要查找的值(可以是字符串或数组) |
$replace | 替换的值(可以是字符串或数组) |
$subject | 被搜索的字符串或数组 |
&$count | 可选参数,如果指定,将被设置为替换发生的次数 |
返回值
返回替换后的字符串或数组。
(2)使用示例
基本字符串替换(不区分大小写)
$text = "Hello World! PHP is great!";
$result = str_ireplace("php", "Python", $text);
echo $result; // 输出: "Hello World! Python is great!"
数组替换
$text = "The quick brown fox jumps over the lazy dog";
$search = ["quick", "brown", "fox"];
$replace = ["slow", "black", "bear"];
$result = str_ireplace($search, $replace, $text);
echo $result; // 输出: "The slow black bear jumps over the lazy dog"
统计替换次数
$text = "Cat, cat, CAT, dog";
$count = 0;
$result = str_ireplace("cat", "tiger", $text, $count);
echo $result; // 输出: "tiger, tiger, tiger, dog"
echo $count; // 输出: 3
(3)在文件上传安全过滤中的应用(有缺陷)
$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");
$file_name = str_ireplace($deny_ext, "", $file_name);
这段代码试图移除文件名中的危险扩展名,但存在安全问题(下文会分析)。
(4)与 str_replace()
的区别
特性 | str_ireplace() | str_replace() |
---|---|---|
大小写敏感 | 不敏感 | 敏感 |
性能 | 稍慢(因为不区分大小写) | 更快 |
(5)安全问题分析
在文件上传过滤中使用 str_ireplace()
存在严重安全隐患:
替换逻辑缺陷
$filename = "test.pHp";
echo str_ireplace(["php"], "", $filename);
// 输出: "test." (只移除了"php"部分)
双扩展名绕过
$filename = "test.php.jpg";
echo str_ireplace(["php"], "", $filename);
// 输出: "test..jpg" (仍可能被解析为PHP文件)
部分匹配问题(双写绕过)
$filename = "test.pphphp";
echo str_ireplace(["php"], "", $filename);
// 输出: "test.pphp" → 处理后变为 "test.php"
(6)安全建议
- 改用白名单机制:只允许已知安全的扩展名
- 使用路径信息函数:
pathinfo()
获取真实扩展名 - 完全重命名文件:不使用用户提供的文件名
- 验证文件内容:检查实际文件类型而非扩展名
(7)安全示例代码
// 安全的白名单方式
$allowed_ext = ['jpg', 'png', 'gif'];
$ext = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $allowed_ext)) {
die("不允许的文件类型");
}
// 完全重命名文件
$new_filename = uniqid() . '.' . $ext;
move_uploaded_file($_FILES['file']['tmp_name'], '/uploads/' . $new_filename);
17. move_uploaded_file() 函数
move_uploaded_file()
是 PHP 中专门用于处理 HTTP 上传文件移动的函数,它是文件上传过程中最关键的安全函数之一。
(1)基本语法
move_uploaded_file(string $from, string $to): bool
参数说明
参数 | 说明 |
---|---|
$from | 文件的路径(即 $_FILES['file']['tmp_name'] ) |
$to | 目标路径(包括新文件名) |
返回值
- 成功返回
true
- 失败返回
false
(2)核心特点
- 安全验证:会额外检查文件是否是通过 HTTP POST 上传的合法文件
- 原子性操作:移动操作是原子的,要么完全成功,要么完全失败
- 自动清理:移动成功后,临时文件会被自动删除
(3)使用示例
基本用法
if (move_uploaded_file(
$_FILES['userfile']['tmp_name'],
'/uploads/' . basename($_FILES['userfile']['name'])
)) {
echo "文件上传成功";
} else {
echo "文件上传失败";
}
安全用法(推荐)
// 定义上传目录
$upload_dir = '/var/www/uploads/';
// 生成唯一文件名
$filename = uniqid('img_') . '.' . pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
$destination = $upload_dir . $filename;
// 验证并移动文件
if (move_uploaded_file($_FILES['file']['tmp_name'], $destination)) {
echo "文件已安全上传";
} else {
echo "文件上传失败";
}
(4)安全注意事项
路径安全:
- 必须检查目标路径是否在允许的目录内
- 防止目录遍历攻击(如
../../../etc/passwd
)
php$upload_dir = '/var/www/uploads/'; $filename = basename($_FILES['file']['name']); // 移除路径信息 $destination = realpath($upload_dir) . DIRECTORY_SEPARATOR . $filename; // 验证目标路径是否在允许的目录内 if (strpos(realpath($destination), realpath($upload_dir)) !== 0) { die("非法路径"); }
文件覆盖:
- 如果目标文件已存在,会被静默覆盖
- 建议使用唯一文件名防止覆盖
权限设置:
- 确保 PHP 进程对目标目录有写权限
- 上传目录不应有执行权限(防止脚本执行)
bashchmod 755 /var/www/uploads # 目录可读可写不可执行
配合其他验证:
- 应先验证文件类型、大小等再移动文件
- 不要仅依赖
move_uploaded_file()
做安全检查
(5)常见错误处理
检查错误代码:
phpif ($_FILES['file']['error'] !== UPLOAD_ERR_OK) { die("上传错误: " . $_FILES['file']['error']); }
验证临时文件:
phpif (!is_uploaded_file($_FILES['file']['tmp_name'])) { die("非法文件来源"); }
检查移动结果:
phpif (!move_uploaded_file(/*...*/)) { error_log("文件移动失败: " . print_r(error_get_last(), true)); die("文件保存失败"); }
(6)完整安全示例
// 配置
$upload_dir = '/var/www/uploads/';
$allowed_types = ['image/jpeg', 'image/png'];
$max_size = 2 * 1024 * 1024; // 2MB
// 验证上传错误
if ($_FILES['file']['error'] !== UPLOAD_ERR_OK) {
die("上传错误: " . $_FILES['file']['error']);
}
// 验证文件大小
if ($_FILES['file']['size'] > $max_size) {
die("文件大小超过限制");
}
// 验证文件类型
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $_FILES['file']['tmp_name']);
finfo_close($finfo);
if (!in_array($mime, $allowed_types)) {
die("不允许的文件类型");
}
// 生成安全文件名
$ext = [
'image/jpeg' => 'jpg',
'image/png' => 'png'
][$mime] ?? 'bin';
$filename = uniqid('img_') . '.' . $ext;
$destination = $upload_dir . $filename;
// 验证目标路径
if (strpos(realpath($upload_dir) . '/', realpath($upload_dir)) !== 0) {
die("非法路径");
}
// 移动文件
if (!move_uploaded_file($_FILES['file']['tmp_name'], $destination)) {
die("文件保存失败");
}
echo "文件上传成功: " . htmlspecialchars($filename, ENT_QUOTES);
18.basename()函数
basename()
是 PHP 中用于获取路径中的文件名部分的函数,它会返回路径中的最后一个名称部分(即文件名或最后一级目录名)。
(1)基本语法
basename(string $path, string $suffix = ""): string
参数说明
参数 | 说明 |
---|---|
$path | 文件路径(可以是相对路径或绝对路径) |
$suffix | 可选参数,如果文件名以这个后缀结尾,则去除该后缀 |
返回值
返回路径中的文件名部分。
(2)使用示例
基本用法
echo basename("/var/www/index.html"); // 输出: "index.html"
echo basename("images/avatar.jpg"); // 输出: "avatar.jpg"
echo basename("../files/data.txt"); // 输出: "data.txt"
去除后缀
echo basename("/var/www/index.html", ".html"); // 输出: "index"
echo basename("report.pdf", ".pdf"); // 输出: "report"
处理不同操作系统路径
// 在Windows系统上
echo basename("C:\xampp\htdocs\index.php"); // 输出: "index.php"
// 在Unix/Linux系统上
echo basename("/home/user/file.txt"); // 输出: "file.txt"
(3)安全应用
在文件上传中的安全使用
// 不安全的方式(可能包含路径遍历)
$unsafe_name = $_FILES['file']['name']; // 可能包含 "../../etc/passwd"
// 安全的方式
$safe_name = basename($_FILES['file']['name']);
配合路径安全检查
$upload_dir = '/var/www/uploads/';
$filename = basename($_FILES['file']['name']);
// 生成完整路径
$destination = $upload_dir . $filename;
// 验证路径是否在允许的目录内
if (strpos(realpath($destination), realpath($upload_dir)) !== 0) {
die("非法路径尝试");
}
(4)注意事项
- 不是万能的:
basename()
只去除路径信息,不验证文件名本身是否安全 - 多字节字符:对于包含非ASCII字符的文件名,可能需要使用
mb_basename()
替代 - 符号链接:不会解析符号链接,直接返回链接名
- 性能:是一个轻量级函数,性能开销很小
(5)常见问题
问题:为什么 basename()
处理后仍然不安全?
回答:basename()
只解决了路径遍历问题,但:
- 不检查文件名是否包含特殊字符
- 不验证扩展名是否合法
- 不防止文件名冲突
(6)安全文件上传的完整示例
// 配置
$upload_dir = '/var/www/uploads/';
$allowed_ext = ['jpg', 'png', 'pdf'];
$max_size = 5 * 1024 * 1024; // 5MB
// 获取安全文件名
$original_name = basename($_FILES['file']['name']);
$ext = strtolower(pathinfo($original_name, PATHINFO_EXTENSION));
// 验证
if (!in_array($ext, $allowed_ext)) {
die("不允许的文件类型");
}
if ($_FILES['file']['size'] > $max_size) {
die("文件大小超过限制");
}
// 生成唯一文件名
$new_filename = uniqid('file_') . '.' . $ext;
$destination = $upload_dir . $new_filename;
// 移动文件
if (move_uploaded_file($_FILES['file']['tmp_name'], $destination)) {
echo "文件上传成功: " . htmlspecialchars($new_filename);
} else {
echo "文件上传失败";
}
(7)相关函数
dirname()
- 获取路径中的目录部分pathinfo()
- 获取文件路径的各个组成部分realpath()
- 返回规范化的绝对路径名
18. parse_str() 函数
parse_str()
是 PHP 中用于将查询字符串(query string)解析为变量的函数,它通常用于处理 URL 中的查询参数。
(1)基本语法
parse_str(string $string, array &$result): void
参数说明
参数 | 说明 |
---|---|
$string | 要解析的输入字符串(通常是 key1=value1&key2=value2 格式) |
&$result | 可选参数,如果提供,结果将存储到这个数组中而不创建变量 |
(2)使用方式
直接创建变量(不推荐)
parse_str("name=John&age=30");
echo $name; // 输出: John
echo $age; // 输出: 30
⚠️ 注意:这种方式会直接在当前作用域创建变量,存在安全风险,不推荐使用。
存储到数组(推荐方式)
$data = [];
parse_str("name=John&age=30", $data);
print_r($data);
/*
输出:
Array
(
[name] => John
[age] => 30
)
*/
(3)主要特点
URL解码:会自动对 URL 编码的值进行解码
phpparse_str("city=New%20York", $data); print_r($data); // 输出: ['city' => 'New York']
处理数组:可以解析出数组结构
phpparse_str("colors[]=red&colors[]=blue", $data); print_r($data); /* 输出: Array ( [colors] => Array ( [0] => red [1] => blue ) ) */
处理多维数组:
phpparse_str("user[name]=John&user[age]=30", $data); print_r($data); /* 输出: Array ( [user] => Array ( [name] => John [age] => 30 ) ) */
(4)安全注意事项
变量注入风险:
php// 危险!会创建 $id 和 $admin 变量 parse_str("id=1&admin=1");
推荐始终使用数组接收结果:
php$data = []; parse_str("id=1&admin=1", $data);
URL编码问题:
- 确保输入字符串是正确编码的
- 可以使用
urldecode()
预处理字符串
输入验证:
- 解析后应对结果数据进行验证和过滤
- 特别是当处理用户提供的查询字符串时
(5)实际应用示例
处理当前URL的查询字符串
// 获取当前URL的查询部分(不含问号)
$queryString = $_SERVER['QUERY_STRING'] ?? '';
// 安全解析
$params = [];
parse_str($queryString, $params);
// 使用参数(先进行验证)
$page = filter_var($params['page'] ?? 1, FILTER_VALIDATE_INT, [
'options' => ['min_range' => 1, 'default' => 1]
]);
echo "当前页码: " . $page;
解析Cookie值
$cookie = "username=JohnDoe; expires=Thu, 18 Dec 2023 12:00:00 UTC; path=/";
parse_str(str_replace('; ', '&', $cookie), $cookieData);
print_r($cookieData);
/*
输出:
Array
(
[username] => JohnDoe
[expires] => Thu
[path] => /
)
*/
(6)相关函数
http_build_query()
- 将数组转换为URL查询字符串(与parse_str相反)urldecode()
- 解码URL编码的字符串filter_input()
- 更安全地获取外部变量
filter_var,$_SERVER
19.create_function()函数
create_function
是 PHP 中的一个函数,用于在运行时动态创建匿名函数(lambda 函数)。不过需要注意的是,这个函数在 PHP 7.2.0 中已被弃用,并在 PHP 8.0.0 中被移除,现在应该使用更现代的匿名函数语法(function(...) {...}
)。
(1)基本语法
create_function(string $args, string $code): string
$args
: 函数参数的字符串表示,如'$a, $b'
$code
: 函数体的字符串表示,如'return $a + $b;'
- 返回值: 返回一个唯一的函数名称字符串
(2)基本示例
$add = create_function('$a, $b', 'return $a + $b;');
echo $add(2, 3); // 输出 5
这相当于现代语法:
$add = function($a, $b) { return $a + $b; };
echo $add(2, 3); // 输出 5
(3)特点与注意事项
安全性问题:
- 由于函数体是字符串,容易导致代码注入漏洞
- 如果
$code
参数来自用户输入,可能被恶意利用
性能:
- 每次调用都会创建一个新函数,影响性能
- 现代匿名函数语法性能更好
调试困难:
- 生成的函数名是自动生成的(如
lambda_1
) - 错误信息难以追踪
- 生成的函数名是自动生成的(如
(4)现代替代方案
使用匿名函数语法(PHP 5.3+):
// 替代create_function
$add = function($a, $b) { return $a + $b; };
// 作为回调函数
usort($array, function($a, $b) { return $a - $b; });
(5)为什么被弃用?
- 安全问题:字符串形式的代码容易导致注入
- 性能问题:不如现代匿名函数高效
- 维护性:现代语法更清晰易读
(6)总结
虽然create_function
在旧代码中可能还会见到,但在新项目中应该使用现代的匿名函数语法。它不仅更安全、性能更好,而且代码更清晰易维护。
如果你正在维护旧代码,考虑逐步替换所有的create_function
调用为匿名函数语法。
20.eval()函数
eval()
是PHP中的一个语言构造器(language construct),用于执行字符串中的PHP代码。
(1)基本语法
mixed eval(string $code)
- 参数:
$code
- 需要执行的PHP代码字符串 - 返回值:返回执行代码的返回值,如果没有return语句则返回NULL
(2)基本用法
$string = 'echo "Hello, World!";';
eval($string); // 输出: Hello, World!
$x = 10;
$result = eval('return $x * 2;');
echo $result; // 输出: 20
(3)特点
- 代码执行:eval执行的代码会继承当前作用域中的变量
- 返回值:可以使用return语句返回一个值
- 错误处理:如果代码中有语法错误,eval会引发解析错误
$name = "Alice";
eval('echo "Hello, $name";'); // 输出: Hello, Alice
(4)安全性问题
PHP的eval同样存在严重的安全风险:
// 危险示例 - 永远不要这样做!
$user_code = $_GET['code'];
eval($user_code); // 用户可以输入恶意代码
(5)安全使用建议
- 避免使用:尽可能寻找替代方案
- 输入过滤:如果必须使用,严格过滤所有输入
- 沙箱环境:考虑使用沙箱环境限制执行权限
(6)替代方案
对于动态代码执行,可以考虑:
- 使用回调函数:
call_user_func()
- 使用变量函数:
$func = 'functionName'; $func();
- 设计模式:如策略模式、工厂模式等
(7)示例:安全使用eval
// 相对安全的用法 - 限制可执行的内容
$allowed_operations = [
'add' => '$a + $b',
'subtract' => '$a - $b'
];
$operation = 'add'; // 从安全来源获取
$a = 10;
$b = 5;
if (array_key_exists($operation, $allowed_operations)) {
$result = eval('return ' . $allowed_operations[$operation] . ';');
echo $result; // 输出: 15
}
21.is_dir()
函数详解
is_dir()
是 PHP 中用于检查指定路径是否为目录的函数,它属于文件系统函数组。
(1)函数语法
bool is_dir ( string $filename )
参数说明
$filename
:要检查的路径。如果是相对路径,则基于当前工作目录进行检查。
返回值
- 如果指定的文件名存在并且是一个目录,则返回
true
- 否则返回
false
(2)基本用法
$path = "/path/to/directory";
if (is_dir($path)) {
echo "$path 是一个目录";
} else {
echo "$path 不是一个目录或不存在";
}
(3)实际应用示例
示例1:检查目录是否存在
$folder = "uploads";
if (is_dir($folder)) {
echo "目录 $folder 存在";
} else {
echo "目录 $folder 不存在";
// 可以在这里创建目录
mkdir($folder);
}
示例2:遍历目录前检查
function listDirectory($dir) {
if (!is_dir($dir)) {
return "错误:$dir 不是有效的目录";
}
$files = scandir($dir);
foreach ($files as $file) {
if ($file != "." && $file != "..") {
# . 表示当前目录本身 .. 表示上级目录(父目录),当使用 scandir()、readdir() 等函数读取目录内容时,返回的数组中总会包含这两个特殊条目,在大多数实际应用中,我们只关心目录中的实际文件和子目录,不需要处理这两个特殊目录,所以排除它们
echo $file . "<br>";
}
}
}
listDirectory("images");
示例3:与文件检查结合使用
$path = "data/config.ini";
if (is_dir($path)) {
echo "这是一个目录";
} elseif (file_exists($path)) {
echo "这是一个文件";
} else {
echo "路径不存在";
}
(4)注意事项
权限问题:如果 PHP 没有权限访问该目录,
is_dir()
也会返回false
。符号链接:对于符号链接,
is_dir()
会检查链接指向的目标是否是目录。性能考虑:频繁调用文件系统函数会影响性能,必要时可以考虑缓存结果。
相对路径:使用相对路径时要注意当前工作目录可能与预期不同。
错误抑制:如果不确定路径是否存在,可以使用
@
抑制可能的警告:phpif (@is_dir($unknown_path)) { // ... }
区分文件和目录:要明确区分文件和目录检查,可以结合使用
is_file()
和is_dir()
。
(5)相关函数
is_file()
- 检查是否是文件file_exists()
- 检查文件或目录是否存在mkdir()
- 创建目录rmdir()
- 删除目录scandir()
- 列出目录中的文件和目录
is_dir()
是目录操作中最基础的检查函数,在进行任何目录操作前使用它可以避免很多错误。
22. mkdir()
函数详解
mkdir()
是 PHP 中用于创建新目录的函数,属于文件系统函数组。
(1)函数语法
bool mkdir ( string $pathname [, int $mode = 0777 [, bool $recursive = false [, resource $context ]]] )
参数说明
参数 | 说明 |
---|---|
$pathname | 要创建的目录路径 |
$mode (可选) | 目录权限,默认 0777(最大权限) |
$recursive (可选) | 是否递归创建所有必需的父目录,默认 false |
$context (可选) | 上下文资源,用于流操作 |
返回值
- 成功创建目录时返回
true
- 失败时返回
false
(2)基本用法
简单创建目录
$dir = "new_folder";
if (!file_exists($dir)) {
if (mkdir($dir)) {
echo "目录 $dir 创建成功";
} else {
echo "目录 $dir 创建失败";
}
} else {
echo "目录 $dir 已存在";
}
设置目录权限
mkdir("protected_folder", 0755); // 设置权限为所有者可读写执行,组和其他可读执行
(3)高级用法
递归创建多级目录
$path = "path/to/nested/directory";
if (!file_exists($path)) {
if (mkdir($path, 0777, true)) { // 第三个参数 true 表示递归创建
echo "多级目录创建成功";
} else {
echo "无法创建目录";
}
}
结合错误处理
function createDirectory($path) {
if (file_exists($path)) {
return true;
}
$old_mask = umask(0); // 临时修改 umask 确保权限设置准确
if (!mkdir($path, 0777, true)) {
umask($old_mask); // 恢复原 umask
throw new Exception("无法创建目录: $path");
}
umask($old_mask);
return true;
}
try {
createDirectory("secure/uploads");
} catch (Exception $e) {
echo $e->getMessage();
}
(4)注意事项
权限问题:
- PHP 需要有目标父目录的写权限
- 创建的目录权限受
umask
影响,实际权限 =$mode & ~umask()
目录已存在:
- 如果目录已存在,
mkdir()
会失败并返回false
- 建议先用
file_exists()
或is_dir()
检查
- 如果目录已存在,
Windows 系统:
- 权限参数
$mode
在 Windows 上被忽略 - 路径分隔符可以使用
/
或\
- 权限参数
安全考虑:
- 避免使用用户输入直接作为目录名
- 必要时使用
basename()
或realpath()
处理路径
性能考虑:
- 递归创建 (
$recursive=true
) 比单独创建每个目录效率高 - 但会一次性占用更多资源
- 递归创建 (
(5)相关函数
rmdir()
- 删除目录is_dir()
- 检查是否是目录file_exists()
- 检查文件或目录是否存在chmod()
- 改变文件/目录权限umask()
- 改变当前的 umask
mkdir()
是目录操作的基础函数,合理使用可以构建复杂的目录结构,特别适合在需要动态创建存储空间的场景中使用。