PHP 判断请求是否来自手机最佳实践(附代码示例) PHP

判断请求是否来自手机是PHP开发者必须掌握的基础技能之一。掌握它不仅能提升开发效率,还能避免很多隐蔽的 Bug。


emer 发布于  2026-10-28 06:51 

PHP 操作Redis缓存快速上手指南(建议收藏) PHP

面试中经常被问到的操作Redis缓存,你真的掌握了吗?本文结合实例,讲解PHP中操作Redis缓存的正确用法。


emer 发布于  2026-10-28 06:50 

PHP密码加密存储 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和密码强度校验构成完整账号安全体系。


emer 发布于  2026-10-22 07:13 

PHP检查文件是否存在 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拼绝对路径,避免相对路径偏移导致找错文件。


emer 发布于  2026-10-22 07:10 

PHP合并数组方法 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,大数组合并注意内存别在循环里反复操作。


emer 发布于  2026-10-21 07:05 

PHP获取文件扩展名 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判断,上传安全还需白名单加文件内容校验和重命名存储。


emer 发布于  2026-10-21 07:01 

PHP获取客户端IP PHP

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需用厂商回源头。


emer 发布于  2026-10-20 07:19 

PHP空合并运算符 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需用旧写法,取到默认值后关键参数仍要校验合法性。


emer 发布于  2026-10-20 07:15 

PHP去除字符串空格 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只管首尾,密码不清洗,全角空格要单独处理,入库前统一规范保证比对一致。


emer 发布于  2026-10-20 07:11 

PHP字符串反转方法 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扩展,大字符串用数组收集再拼接提升性能。


emer 发布于  2026-10-20 07:05 

PHP时间戳转日期 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失败要判空。入库统一存秒级时间戳,展示层再格式化,比存字符串日期更好比较和索引。


emer 发布于  2026-10-19 07:37 

PHP判断数组为空 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严格判断。


emer 发布于  2026-10-19 07:31 

PHP遍历关联数组 PHP

PHP遍历关联数组

引言

关联数组用键名而非数字下标组织数据,配置项、接口返回的JSON转成数组后都是关联数组。遍历要同时拿到键和值,foreach是最常用的方式,不同场景配合array_keys、array_walk更灵活。

foreach取键和值

$user = ['name'=>'刘馨怡','age'=>26,'city'=>'连云港'];
foreach ($user as $key => $value) {
    echo "$key: $value\n";
}

$key => $value同时拿到键名和值,最通用。

只取值

foreach ($user as $value) {
    echo $value;
}

不需要键名时省略$key。

用键名取单个值

echo $user['name'];
// 判断键是否存在
if (isset($user['email'])) {
    echo $user['email'];
}

取单值直接下标访问,先isset避免未定义键报错。

array_keys取所有键

$keys = array_keys($user);
print_r($keys); // ['name','age','city']

需要单独处理键名列表时用。

array_walk回调遍历

array_walk($user, function($value, $key) {
    echo "$key => $value";
});

适合把遍历逻辑封装成回调。

二维关联数组遍历

$list = [
    ['id'=>1,'name'=>'A'],
    ['id'=>2,'name'=>'B']
];
foreach ($list as $row) {
    echo $row['id'].'-'.$row['name'];
}

循环里再按键名取子元素。

注意事项

  • foreach不会改变原数组指针,比each安全;
  • 遍历中不要随意增删键,可能导致迭代异常;
  • 关联数组键名大小写敏感;
  • 值可能为null时用array_key_exists判断键是否存在,isset对null返回false;
  • 大数组遍历时注意内存,避免在循环内重复计算。

总结

关联数组遍历首选foreach的$key => $value形式,单值直接下标加isset判断,取键名用array_keys,封装逻辑用array_walk,二维数组外层foreach内层按键取值。掌握这几种就能应对配置、接口数据等绝大多数场景。


emer 发布于  2026-10-19 07:27 

PHP cURL发送POST PHP

PHP cURL发送POST

引言

后端对接第三方接口、登录模拟、提交表单,PHP最常用cURL扩展发HTTP请求。file_get_contents只能简单GET,cURL能控制POST、请求头、Cookie、超时和错误处理,是对接API的标配。

基础POST表单

$ch = curl_init('https://api.example.com/login');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, [
    'username' => 'user01',
    'password' => '123456',
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 结果返回字符串而非直接输出
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$res = curl_exec($ch);
$err = curl_error($ch);
curl_close($ch);

POSTFIELDS传数组即multipart表单,接口收到的是form-data。

发送JSON

对接REST接口要手动编码并声明头:

$json = json_encode(['name' => '测试', 'age' => 18]);
curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer ' . $token,
]);

注意JSON方式POSTFIELDS传字符串,别传数组。

带Cookie维持登录

curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookie.txt');   // 第一次保存
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookie.txt');  // 第二次读取

模拟登录再访问需鉴权页面时,用cookie文件维持会话。

错误与状态码处理

if ($err) { echo '请求失败:' . $err; }
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$data = json_decode($res, true);

别只看返回体,必须判断$code和curl_error,500/404也要处理。

注意事项

  • 关SSL校验仅限本地测试,生产保留CURLOPT_SSL_VERIFYPEER;
  • 超时必须设,防止接口慢拖垮进程;
  • HTTPS证书异常先排查证书链,别长期关闭校验;
  • 敏感token放请求头,别写进URL参数;
  • 用完curl_close释放句柄。

总结

cURL POST先init,设CURLOPT_POST和POSTFIELDS,表单传数组、JSON传字符串并带头,RETURNTRANSFER取回结果,配合超时、错误判断和状态码,生产保留SSL校验。掌握这套就能稳定对接绝大多数第三方API。


emer 发布于  2026-10-19 07:23 

PHP二维数组排序 PHP

PHP二维数组排序

引言

从数据库查出的列表通常是二维数组,要按价格、销量、时间某一字段升降序排列,单维的sort/rsort直接用会把结构打乱。需要按指定键排序,常用array_multisort和usort两种思路。

array_multisort按字段排

$goods = [
    ['name' => 'A商品', 'price' => 99],
    ['name' => 'B商品', 'price' => 39],
    ['name' => 'C商品', 'price' => 69],
];
// 抽出price列作为排序依据
$price = array_column($goods, 'price');
array_multisort($price, SORT_DESC, $goods);
print_r($goods); // 价格从高到低

array_column先取单列,multisort按该列降序,主数组同步跟着重排。

多字段组合排序

先按销量降序,销量相同再按价格升序:

$sales = array_column($goods, 'sales');
$price = array_column($goods, 'price');
array_multisort($sales, SORT_DESC, $price, SORT_ASC, $goods);

usort自定义比较

规则复杂或中文拼音排序时用usort:

usort($goods, function($a, $b) {
    return $a['price'] <=> $b['price']; // 升序
});

太空船运算符<=>比较大小,降序交换$a、$b即可。

注意事项

  • array_multisort会改变原数组顺序,需要原数据先备份;
  • 比较的字段类型要一致,避免字符串和数字混比;
  • usort是稳定排序,同值元素相对位置保留;
  • 中文排序可配合intl扩展或自定义映射,别直接靠默认strcmp;
  • 数据量大时,优先在SQL层ORDER BY,PHP排序只做内存小列表。

总结

简单单字段排序用array_column加array_multisort,多字段依次传入,复杂规则用usort加<=>比较。能在MySQL ORDER BY解决的就别把数据拉到PHP再排,减轻内存压力。


emer 发布于  2026-10-19 07:20 

PHP字符串截取方法 PHP

PHP字符串截取方法

引言

截取标题摘要、手机号中间脱敏、按字数截断列表页,是PHP开发里最高频的操作。很多人直接用substr处理中文,结果出现半个汉字的乱码,关键在于substr按字节切,中文UTF-8占3个字节。

substr按字节截取

$str = "hello世界";
echo substr($str, 0, 5); // 输出 hello,中文场景容易切坏

substr只适合纯英文ASCII,遇到中文按字节偏移,可能把一个汉字拆成两半。

mb_substr按字符截取

处理中文要用多字节扩展mbstring:

echo mb_substr("hello世界", 0, 5, 'UTF-8');

第四个参数指定编码UTF-8,按"字"而不是"字节"切,中文完整不烂。

带省略号的摘要截断

function cutText($text, $len = 20) {
    if (mb_strlen($text, 'UTF-8') <= $len) return $text;
    return mb_substr($text, 0, $len, 'UTF-8') . '...';
}

先mb_strlen算字符长度,超长再截并补省略号,列表页摘要常用。

手机号中间脱敏

$phone = "13812345678";
echo substr($phone, 0, 3) . '****' . substr($phone, -4);
// 138****5678

手机号纯数字,substr完全够用,用-4取后四位。

注意事项

  • 中文必须mb_substr并显式传UTF-8,别依赖默认编码;
  • 确认服务器装了mbstring扩展,php -m查看;
  • 表情、生僻字占多个字节,更要按字符切;
  • 截断前先判断长度,避免补省略号反而超长。

总结

纯英文数字用substr,含中文统一mb_substr指定UTF-8,摘要先mb_strlen判断再补省略号,手机号脱敏用substr取头尾。选对函数就不会出现半个汉字乱码。


emer 发布于  2026-10-19 07:16 

PHP数组去重排序 PHP

PHP数组去重排序

引言

数组是PHP最常用的数据结构,去重和排序是开发中反复出现的基础需求。掌握内置函数的正确用法,比自己写循环更高效,也更不容易出错。

一维数组去重

$arr = [3, 1, 2, 3, 1, 5];
$unique = array_unique($arr);
//结果保留首次出现的值,键名不变

注意array_unique默认按字符串比较,去重后键名可能不连续,需要重新索引:

$unique = array_values(array_unique($arr));

关联数组按字段去重

$users = [
    ['id'=>1,'name'=>'张三'],
    ['id'=>2,'name'=>'李四'],
    ['id'=>1,'name'=>'张三副本'],
];
$seen = [];
$unique = array_filter($users, function($item) use (&$seen) {
    if (in_array($item['id'], $seen)) return false;
    $seen[] = $item['id'];
    return true;
});

排序函数区分

  • sort():值升序,重置键名;
  • rsort():值降序;
  • asort():值升序,保留键名;
  • ksort():按键名升序;
  • krsort():按键名降序。
$score = ['张三'=>88,'王五'=>95,'李四'=>76];
arsort($score); //按分数降序,保留姓名键名
print_r($score);

自定义排序

$users = [['age'=>20],['age'=>35],['age'=>28]];
usort($users, function($a, $b) {
    return $a['age'] <=> $b['age']; //太空船运算符比较
});

注意事项

  • array_unique对多维数组不能直接按字段去重;
  • sort系列会改变原数组,返回bool;
  • 中文排序建议用Collator或mb方式;
  • 去重后及时array_values重置索引,避免后续遍历异常。

总结

一维去重用array_unique加array_values,多维按字段用array_filter配合标记数组。排序先想清是否保留键名,普通值排序用sort/rsort,关联数组用asort/ksort,复杂规则用usort自定义比较。


emer 发布于  2026-10-18 07:30 

PHP错误日志排查 PHP

PHP错误日志排查

引言

线上页面白屏、500错误,前台看不到具体原因,靠猜很难定位。正确做法是开启并查看PHP错误日志,从日志里拿到文件、行号、错误类型,再针对性修复。

先确认日志位置

在phpinfo页面搜索error_log,或查看php.ini配置:

#查看php.ini错误日志配置
php -i | grep error_log
#常见位置
tail -f /var/log/php-fpm/www-error.log
tail -f /var/log/httpd/php_errors.log

宝塔、面板环境一般在日志菜单里可直接打开PHP错误日志。

开发环境临时显示错误

<?php
ini_set('display_errors',1);
ini_set('error_reporting',E_ALL);
error_reporting(E_ALL);

生产环境不要直接display_errors到页面,统一写日志。

日志格式解读

一条错误日志通常包含:错误级别、报错文件、行号、错误信息。

[12-Oct-2026 07:00:00] PHP Fatal error:
Call to undefined function xxx() in /www/app/index.php on line 18

含义:第18行调用了不存在的函数,检查函数是否引入、命名是否拼错。

常见错误类型

  • Fatal error:致命错误,程序中断,如调用不存在的类、函数;
  • Warning:警告不中断,如参数类型不对;
  • Notice:提示,未定义变量;
  • Deprecated:废弃语法,未来版本不兼容。

排查流程

  1. 复现问题,记录访问时间;
  2. 按时间过滤日志,定位对应报错;
  3. 打开报错文件对应行号;
  4. 修复后清空日志再验证。

总结

PHP排错第一步永远是看错误日志,而不是反复刷新页面。开发环境显示错误,生产环境写日志,按时间和行号快速定位,养成记录日志的习惯。


emer 发布于  2026-10-18 07:23 

PHP文件上传校验 PHP

PHP文件上传校验

引言

文件上传是网站高频功能,也是最容易出安全问题的地方。只判断后缀名,黑客可以把木马改名为图片绕过,上传到服务器执行。上传必须做多层校验。

必须校验的内容

  1. 检查上传错误码,确认文件确实上传成功;
  2. 限制文件大小,防止超大文件占满磁盘;
  3. 白名单校验扩展名,只允许jpg/png等指定类型;
  4. 读取文件真实MIME类型,不能只信前端传的type;
  5. 重新生成随机文件名,避免原文件名覆盖或路径穿越;
  6. 上传目录禁止执行PHP脚本。

基础校验示例

<?php
$allow = ['jpg','png','gif'];
$ext = strtolower(pathinfo($_FILES['file']['name'],PATHINFO_EXTENSION));
if(!in_array($ext,$allow)){
    exit('类型不允许');
}
if($_FILES['file']['size'] > 2*1024*1024){
    exit('文件过大');
}
//获取真实类型
$info = getimagesize($_FILES['file']['tmp_name']);
if(!$info) exit('不是合法图片');
//随机重命名
$newName = uniqid().'.'.$ext;
move_uploaded_file($_FILES['file']['tmp_name'],'./upload/'.$newName);

服务端加固

  • Nginx/Apache配置上传目录禁止解析php;
  • 图片上传后重新压缩生成,破坏木马脚本;
  • 关闭上传目录的执行权限;
  • 定期扫描上传目录文件。

总结

文件上传安全核心:不信任前端、白名单、校验真实类型、随机改名、目录隔离。多层校验才能防止webshell上传,保护服务器安全。


emer 发布于  2026-10-18 07:19 

PHP请求参数过滤 PHP

PHP请求参数过滤

引言

Web接口接收GET、POST参数,如果不做过滤校验,容易出现SQL注入、XSS、业务逻辑漏洞。PHP开发接口时,参数过滤是安全第一道防线,所有外部输入都不可信。

基础过滤思路

  1. 类型强制转换:数字参数直接转为int;
  2. 长度限制,防止传入超大字符串;
  3. 正则匹配手机号、邮箱等固定格式;
  4. 输出到页面时做html转义,防止XSS;
  5. 入库前转义或者使用PDO预编译防止注入。

代码示例

<?php
// 获取GET参数
$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
$name = trim($_GET['name'] ?? '');

// 长度校验
if(mb_strlen($name)>50){
    exit("参数过长");
}

// 输出到页面,防XSS
echo htmlspecialchars($name,ENT_QUOTES);

框架层面最佳实践

TP6、Laravel等框架自带验证器,推荐优先使用框架验证,不用手写大量if判断。

// ThinkPHP6验证示例
$validate = new \think\Validate([
    'id'=>'require|integer',
    'name'=>'require|max:50'
]);

避坑点

不要简单依靠addslashes防御注入,推荐PDO预处理语句;参数过滤不要只在前端校验,后端必须二次校验,前端校验可被绕过。

总结

任何外部传入的数据都要校验,区分类型校验、长度校验、格式校验、输出转义四层防护,降低Web安全漏洞风险,是PHP项目开发的基础规范。


emer 发布于  2026-10-18 07:14 
'); })();