PHP cURL发送GET请求案例讲解(附代码示例)
很多初学者在学习PHP时,都会遇到cURL发送GET请求相关的问题。网上的资料零散且质量参差不齐,本文尝试用一篇文章把cURL发送GET请求讲清楚。
PHP密码加密存储
PHP密码加密存储
引言
用户密码绝不能明文入库,泄露即灾难。MD5、SHA1是哈希算法但速度快、可被彩虹表破解,不适合存密码。PHP 5.5以后内置password_hash和password_verify,用bcrypt自带盐值和成本因子,是现在注册登录系统的标准做法。
注册:生成哈希入库
$pwd = $_POST['password'];
$hash = password_hash($pwd, PASSWORD_DEFAULT);
// $2y$10$.... 直接把这串哈希存数据库
$db->insert('users', ['password' => $hash]);
PASSWORD_DEFAULT默认bcrypt,自动生成随机盐,算法和成本因子都记录在哈希串里,不需要单独存盐字段。
登录:校验密码
$row = $db->find('users', ['username' => $username]);
if ($row && password_verify($inputPwd, $row['password'])) {
// 密码正确,登录成功
} else {
// 用户名或密码错误
}
password_verify自动识别哈希里的算法和盐,比对用户输入的明文,返回布尔值。
指定成本因子
$hash = password_hash($pwd, PASSWORD_BCRYPT, ['cost' => 12]);
cost越高计算越慢、抗暴力破解越强,默认10。服务器性能允许可适当调高,太慢会影响登录接口响应。
判断哈希是否需要升级
if (password_needs_rehash($row['password'], PASSWORD_DEFAULT)) {
$newHash = password_hash($inputPwd, PASSWORD_DEFAULT);
$db->update('users', ['password' => $newHash], $row['id']);
}
算法或成本升级后,老用户登录时校验通过顺便重算哈希,平滑迁移不用强制改密。
错误做法对比
# 不要这样
md5($pwd); # 无盐,彩虹表秒破
sha1($pwd . $salt); # 自制盐方案,仍可GPU爆破
crypt($pwd); # 旧接口,参数难控
自己拼盐、用通用哈希函数存密码都是安全隐患,直接用内置密码函数。
注意事项
- 数据库密码字段长度留够60字符以上,bcrypt哈希约60位;
- 别自己实现加盐哈希,密码函数已封装随机盐和算法版本;
- 登录失败统一提示"用户名或密码错误",别区分哪个不存在;
- 密码强度前端后端双重校验,长度、复杂度、常见弱密码黑名单;
- 哈希不可逆,忘记密码只能重置,不能找回原密码;
- 配合HTTPS传输,密码在传输层也要加密保护。
总结
注册用password_hash生成bcrypt哈希入库,登录用password_verify校验,自带随机盐无需单独存盐。cost成本因子按服务器性能调整,password_needs_rehash在用户登录时平滑升级旧哈希。禁止用MD5、SHA1或自制盐方案存密码,密码字段预留60字符以上,登录失败提示统一,配合HTTPS和密码强度校验构成完整账号安全体系。
PHP检查文件是否存在
PHP检查文件是否存在
引言
上传头像、读取配置、缓存命中判断之前,都要先确认文件是否存在。PHP里file_exists判断文件或目录,is_file专门判断普通文件,is_dir判断目录,三者语义不同,用错会把目录当成文件导致后续读取报错。
file_exists 判断存在
$path = './config/config.php';
if (file_exists($path)) {
echo '文件存在';
} else {
echo '文件不存在';
}
文件或目录存在都返回true,路径不存在、权限不可访问返回false。
is_file 判断是普通文件
if (is_file($path)) {
echo '这是一个普通文件';
}
目录、符号链接、管道都返回false,读取文件前用它最稳妥。
is_dir 判断目录
$dir = './uploads';
if (is_dir($dir)) {
echo '目录存在';
}
专门判断目录,适合上传前检查目录是否已建。
判断可读性再操作
if (is_file($path) && is_readable($path)) {
$content = file_get_contents($path);
}
存在不代表可读,权限不足时file_get_contents会警告,先加is_readable判断。
路径拼接注意
$file = __DIR__ . '/../data/' . $name;
用绝对路径拼接,避免相对路径受当前工作目录影响,跨目录包含时不容易找错文件。
注意事项
- file_exists缓存了文件状态,删改文件后可用clearstatcache清缓存;
- 判断普通文件用is_file,别只靠file_exists,目录会误判;
- 检查存在后还要is_readable/is_writable,权限是另一道关;
- 文件名含中文或特殊字符注意编码统一,避免误判不存在;
- 防路径穿越,用户传入的文件名先过滤..和绝对路径;
- 大量循环判断文件时注意stat缓存带来的性能影响。
总结
file_exists判断文件或目录是否存在,is_file判断普通文件,is_dir判断目录,is_readable/is_writable判断读写权限。读取配置、上传文件、缓存命中前先做存在性和权限判断。注意clearstatcache清理状态缓存,用户传入文件名要防路径穿越,跨目录用DIR拼绝对路径,避免相对路径偏移导致找错文件。
PHP合并数组方法
PHP合并数组方法
引言
合并两个或多个数组是日常开发高频操作。索引数组用array_push或加号,关联数组用array_merge,+号和array_merge在键冲突时行为完全不同,搞混会踩坑。
array_merge 合并
$a = [1, 2];
$b = [3, 4];
$c = array_merge($a, $b);
// [1, 2, 3, 4]
索引数组会重排数字键,关联数组同名键后者覆盖前者。
加号 + 保留原键
$a = ['a' => 1, 'b' => 2];
$b = ['b' => 99, 'c' => 3];
$c = $a + $b;
// ['a'=>1,'b'=>2,'c'=>3] 左侧优先
+号左边数组的键优先,同键不覆盖,保留左侧值。
array_merge vs + 的区别
$a = ['a' => 1];
$b = ['a' => 99];
array_merge($a, $b); // ['a'=>99] 后者覆盖
$a + $b; // ['a'=>1] 左侧保留
关联数组合并要谁覆盖谁,选对函数很关键。
数组合并并去重
$all = array_unique(array_merge($a, $b));
合并后去重,适合标签、权限码这类集合。
追加元素到数组
$list[] = 'new';
array_push($list, 'a', 'b');
单元素直接[]追加最简洁,多元素用array_push。
注意事项
- array_merge会重置数字索引,+号保留原数字键;
- 关联数组同名键,array_merge后者覆盖,+号左侧优先;
- 合并大数组注意内存,别在循环里反复merge;
- array_merge递归合并多维要用array_replace_recursive;
- 空数组合并不会报错;
- 合并后记得检查是否都是数组类型再合并。
总结
array_merge合并数组,数字键重排、关联键后者覆盖;+号运算符左侧优先不覆盖。合并去重用array_unique包一层,追加元素直接[]最简洁。注意两者在同名键上的覆盖方向完全相反,多维递归合并用array_replace_recursive,大数组合并注意内存别在循环里反复操作。
PHP获取文件扩展名
PHP获取文件扩展名
引言
文件上传、附件处理、图片裁剪时都要先拿到后缀判断类型。别用explode切字符串硬切,pathinfo是PHP专门解析路径的函数,能干净返回扩展名、文件名、目录名各部分。
pathinfo 标准用法
$file = '/var/www/upload/photo.2024.jpg';
$info = pathinfo($file);
echo $info['extension']; // jpg
echo $info['filename']; // photo.2024
echo $info['dirname']; // /var/www/upload
一次返回路径各组成部分,最稳妥。
只取扩展名
$ext = pathinfo($file, PATHINFO_EXTENSION);
// jpg
传第二个常量直接拿扩展名,省一次数组取值。
处理无扩展名文件
$ext = pathinfo('README', PATHINFO_EXTENSION);
// 空字符串,不会报错
没扩展名时返回空串,记得做空判断。
大写转小写统一
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
用户传JPG、Jpg、jpg都归一,上传校验用统一小写比较。
不要用explode硬切
// 反面例子:photo.2024.jpg 会切错
$parts = explode('.', $file);
$ext = end($parts);
多级点号文件名会取错,pathinfo按最后一个点正确取后缀。
注意事项
- 扩展名不等于真实MIME类型,校验文件内容要用finfo;
- 扩展名可伪造,上传安全不能只信后缀;
- PATHINFO_EXTENSION对".htaccess"这种点开头文件返回空;
- 中文文件名不受影响,pathinfo按字节正确处理;
- 取到扩展名后trim并strtolower再做白名单校验;
- 别把扩展名当安全依据,配合大小限制和重命名存储。
总结
pathinfo($file, PATHINFO_EXTENSION)取扩展名最稳,filename拿文件名、dirname拿目录,无扩展名返回空串记得判断,业务里统一strtolower小写。不要用explode切多级点号文件名。扩展名只是展示用,真实类型靠finfo判断,上传安全还需白名单加文件内容校验和重命名存储。
PHP获取客户端IP
PHP获取客户端IP
引言
记录访问日志、做地域统计或IP限定时要拿真实访客IP。经过Nginx反代或CDN后,REMOTE_ADDR是代理地址,真实IP藏在转发头里,直接取单一字段容易拿到代理IP,需要按可信链兜底并校验。
直连场景
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
没有反代时,这就是直连客户端IP。
反代后取真实IP
$headers = [
'HTTP_X_FORWARDED_FOR',
'HTTP_X_REAL_IP',
'HTTP_CLIENT_IP',
];
foreach ($headers as $h) {
if (!empty($_SERVER[$h])) {
$ip = explode(',', $_SERVER[$h])[0];
break;
}
}
X-Forwarded-For可能是逗号分隔的IP链,取第一个才是原始客户端。
封装函数
function getClientIp(): string {
foreach (['HTTP_X_FORWARDED_FOR','HTTP_X_REAL_IP'] as $h) {
if (!empty($_SERVER[$h])) {
$ip = trim(explode(',', $_SERVER[$h])[0]);
if (filter_var($ip, FILTER_VALIDATE_IP)) return $ip;
}
}
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
filter_var校验格式,非法就回退到REMOTE_ADDR。
代理头必须由服务端写入
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
头要由可信Nginx设置,不能完全信任客户端自带。
注意事项
- XFF是逗号链,取最左原始IP;
- 客户端可伪造XFF,只信任自己配置的代理;
- filter_var校验IPv4/IPv6格式;
- 不要用伪造IP做安全鉴权,登录风控仍需服务端;
- CDN场景真实IP在CDN回源头,需查厂商文档;
- 内网代理REMOTE_ADDR是网关IP,别当访客IP。
总结
直连取REMOTE_ADDR,反代后从X-ForwardedFor、X-Real-IP按序兜底,XFF逗号链取第一个,filter_var校验后回退,代理头由可信Nginx写入。关键是客户端可伪造转发头,只信任自己配置的代理链,IP适合统计不适合单独做安全判定,CDN需用厂商回源头。
PHP空合并运算符
PHP空合并运算符
引言
从请求数组里取值时,直接访问不存在的键会触发Notice报错。isset加三元判断写法啰嗦,PHP7引入的??空合并运算符一行搞定,且不存在键时不报Notice,是接口参数处理的高频写法。
基本用法
$page = $_GET['page'] ?? 1;
键存在且非null就取它的值,否则取默认值1,全程不报Notice。
对比老式写法
// 旧写法,啰嗦且易漏isset
$page = isset($_GET['page']) ? $_GET['page'] : 1;
// 新写法,等价且简洁
$page = $_GET['page'] ?? 1;
两段逻辑完全等价,新写法可读性明显更好。
链式兜底
$name = $input['name'] ?? $request['name'] ?? '匿名用户';
依次尝试,第一个非null的值被采用,最后一个兜底默认值。
数组嵌套取值
$city = $data['user']['city'] ?? '未知城市';
中间任何一层键缺失都安全返回默认值,不会因层级不存在报错。
??与||的区别
// ??只判断null和未定义,空字符串0都保留
$q = $_GET['q'] ?? '';
// ||会把0、空字符串当假,业务语义不同
注意0和空字符串不是null,??会原样保留。
赋值场景:合并赋值
$config['debug'] ??= false;
??=只在键不存在或为null时赋值,已存在则不动。
注意事项
- ??只在未定义或null时兜底,空串、0、false不算空;
- 数组深层取值安全,但类型仍要自行校验;
- 不要用??替代输入过滤,默认值之外仍需验证;
- PHP5不支持??,老环境需改用isset三元;
- 链式写法注意短路,前面命中即返回;
- 业务关键参数兜底后仍要做合法性范围检查。
总结
??判断键是否存在或为null,缺省返回默认值且不报Notice,替代isset三元,支持链式兜底和数组深层取值,??=实现条件赋值。区别于||,0和空字符串会被保留不会被兜底,PHP5需用旧写法,取到默认值后关键参数仍要校验合法性。
PHP去除字符串空格
PHP去除字符串空格
引言
用户输入、表单提交常带首尾空格甚至全角空格、换行,入库前不清洗会导致登录比对失败、查询匹配不上、统计失真。trim系列处理首尾,preg_replace处理中间多余空格,按需选用。
去除首尾空白:trim
$name = trim(" 张三 ");
// 结果:张三,首尾空格、制表符、换行一并去掉
trim默认去空格、tab、换行、回车、null字节等首尾空白。
只去左侧:ltrim
$path = ltrim("/var/www/", "/");
// 结果:var/www/,第二个参数指定要剥掉的字符集
只去右侧:rtrim
$file = rtrim("photo.jpg/", "/");
// 结果:photo.jpg/,只剥右边指定字符
自定义去除字符
trim($str, " \t\n,");
第二个参数列出要去除的字符,不止空格,可同时去逗号、换行。
去掉中间多余空格
$str = preg_replace('/\s+/', ' ', $str);
// 多个连续空白压成一个空格
$str = preg_replace('/\s/', '', $str);
// 彻底删除所有空格和空白
trim管首尾,中间空格必须用正则。
处理全角空格
$str = str_replace(' ', '', $str);
// 全角空格\u3000普通trim去不掉,要单独替换
数组批量清洗
$clean = array_map('trim', $_POST);
对整个表单数组逐个trim,登录注册入口常用。
注意事项
- trim只处理首尾,别指望它删中间空格;
- 全角空格普通trim无效,需str_replace单独处理;
- 密码字段不要trim,避免误改用户输入;
- 第二个参数是字符集不是整串,按字符逐个剥;
- 入库前统一清洗,查询和比对才一致;
- 中文人名中间正常空格要保留,只压连续空白。
总结
trim去首尾空白,ltrim去左,rtrim去右,第二个参数自定义字符集,中间连续空格用preg_replace压成一个或全删,全角空格单独str_replace,表单用array_map批量清洗。记住trim只管首尾,密码不清洗,全角空格要单独处理,入库前统一规范保证比对一致。
PHP字符串反转方法
PHP字符串反转方法
引言
字符串反转是面试和笔试高频小题,业务里也常用于生成验证码、翻转密文顺序、做简单加密。PHP内置strrev一行搞定,但处理中文时要注意字节和字符的区别。
英文字符反转:strrev
$str = "hello world";
echo strrev($str); // dlrow olleh
strrev按字节反转,英文和数字没有问题,性能也最高。
中文字符串反转
strrev直接转中文会乱码,因为中文UTF-8占3个字节,按字节切会把一个字拆坏。需要按字符处理:
$str = "技术分享";
$len = mb_strlen($str, 'UTF-8');
$result = '';
for ($i = $len - 1; $i >= 0; $i--) {
$result .= mb_substr($str, $i, 1, 'UTF-8');
}
echo $result; // 享分术技
用mb_strlen取字符数,mb_substr按字符回取,保证每个完整汉字不被拆开。
数组方式实现
$arr = preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY);
echo implode('', array_reverse($arr));
u修饰符按UTF-8切字符成数组,array_reverse翻转后再拼回去。
随机打乱:str_shuffle
echo str_shuffle("abc123"); // 每次结果随机
注意str_shuffle同样按字节,中文打乱不适用,验证码里建议只放英文数字。
注意事项
- strrev只适合单字节英文,中文必须走mb系列;
- 生产验证码用str_shuffle后再截取长度,别用中文;
- 反转不等于加密,明文可还原,别拿来做真正加密;
- mb扩展缺失时先检查extension_loaded('mbstring');
- 超大字符串循环回拼建议用数组收集后implode,减少字符串反复复制。
总结
英文用strrev一行反转,中文用mb_strlen加mb_substr按字符倒取,数组preg_split配合array_reverse也能实现,随机打乱用str_shuffle但仅限英文数字。记住字节反转和字符反转的区别,中文场景务必走mb扩展,大字符串用数组收集再拼接提升性能。
PHP时间戳转日期
PHP时间戳转日期
引言
数据库里常存Unix时间戳(秒级整数),展示到页面要转成可读日期;反过来用户选的日期也要转成时间戳入库。date()和strtotime()是这对基础转换工具,理解格式符和时区才能避免差8小时。
时间戳转日期
$ts = 1760000000;
echo date('Y-m-d H:i:s', $ts);
date()第二个参数缺省就是当前时间time(),传指定时间戳则格式化该时刻。
当前时间戳
echo time(); // 秒级时间戳
echo microtime(true); // 带微秒的浮点时间戳
日期字符串转时间戳
$ts = strtotime('2026-10-19 08:30:00');
echo $ts;
strtotime支持英文相对表达式:
strtotime('tomorrow');
strtotime('+1 week');
strtotime('next Monday');
DateTime面向对象写法
$dt = new DateTime('@' . $ts);
echo $dt->format('Y-m-d H:i:s');
$d = DateTime::createFromFormat('Y-m-d', '2026-10-19');
echo $d->getTimestamp();
复杂时区和格式解析用DateTime更稳。
常用格式符
- Y四位年,m两位月,d两位日,H24小时,i分,s秒;
- 带毫秒用v,星期用D/l,12小时制用g/a。
注意事项
- 服务器时区不对会差8小时,先date_default_timezone_set('Asia/Shanghai');
- date()对1970前或2038前后32位系统有边界问题;
- strtotime解析失败返回false,要判空再用;
- 毫秒时间戳除以1000再传给date();
- 跨时区存储建议统一UTC,展示时再转本地;
- MySQL存int(10)秒级,避免用字符串日期做比较。
总结
时间戳转日期用date('Y-m-d H:i:s', $ts),日期转时间戳用strtotime(),复杂解析用DateTime。先设好Asia/Shanghai时区,毫秒先除以1000,strtotime失败要判空。入库统一存秒级时间戳,展示层再格式化,比存字符串日期更好比较和索引。
PHP判断数组为空
PHP判断数组为空
引言
接口返回、查询结果、表单收集都要先判断数组有没有数据,再决定走空分支还是遍历。empty、count、!$arr三种写法结果接近,但适用边界不同,选错会把0或null误判。
empty最常用
$list = [];
if (empty($list)) {
echo '数组为空';
}
empty对空数组、未定义变量、null、0、''都返回true,不会报未定义警告,最适合先判空。
count取元素个数
if (count($arr) === 0) {
echo '没有元素';
}
精确判断元素数量,count为0才是空;注意$arr本身未定义会警告,先isset。
逻辑非写法
if (!$arr) {
echo '空';
}
短但要求$arr一定是已初始化数组,未定义会Notice。
判断键是否为空
不是判断整体,而是某键值:
if (empty($arr['name'])) {
echo 'name为空';
}
避免直接取未定义键。
多维数组判空
if (!empty($arr['items']) && is_array($arr['items'])) {
foreach ($arr['items'] as $item) {}
}
先确认外层键存在且是数组,再遍历内层。
注意事项
- empty(0)和empty('')也为true,数字0场景别用empty判数组元素;
- count第二个参数1可统计多维总元素;
- 严格比较用count($arr)===0;
- 数组里全是空字符串不等于空数组;
- 查询无结果返回[]而非null,按空数组处理;
- 结合isset防止未定义键警告。
总结
整体判空首选empty($arr)防未定义,精确统计用count($arr)===0,短逻辑非要求变量已初始化,判单个键用empty($arr['key']),多维先确认键存在再遍历。注意empty对0和空串也为真,数字场景改用count严格判断。