PHP中的null字符问题

由于 PHP 的文件系统操作是基于 C 语言的函数的,所以它可能会以您意想不到的方式处理 Null 字符。 Null字符在 C 语言中用于标识字符串结束,一个完整的字符串是从其开头到遇见 Null 字符为止。
借用官方手册中的一个例子:

$file = $_GET['file']; // "../../etc/passwd\0"
if (file_exists('/home/wwwrun/'.$file.'.php')) {
    // file_exists will return true as the file /home/wwwrun/../../etc/passwd exists
    include '/home/wwwrun/'.$file.'.php';
    // the file /etc/passwd will be included
}

以上代码在PHP5.3以前版本中,文件/etc/passwd将会被加载。对以后的版本没有影响。
虽然这个遇到\0会把字符截断的问题已经被修复了,但是在PHP中文件系统相关函数中还是有一点影响的,比如file_exists(),is_file()等。
如下代码:

$filename = "/etc/passwd\0"; 
$res = file_exists($filename); 
var_dump($res);

以上代码的执行结果如下:
Warning: file_exists() expects parameter 1 to be a valid path, string given in /usr/local/var/www/a.php on line 3
/usr/local/var/www/a.php:4:null

解决办法

对null字符进行替换

$input = str_replace(chr(0), '', $input);

重新认识PHP中的set_time_limit

想必大家都已经知道set_time_limit函数的作用,即设置脚本最大执行时间。但是这个函数还是有一些细节有些人可能没太注意。

首先先看一下set_time_limit函数的解释。设置允许脚本运行的时间,单位为秒。如果超过了此设置,脚本返回一个致命的错误。默认值为30秒,或者是在php.ini的max_execution_time被定义的值,如果此值存在。当此函数被调用时,set_time_limit()会从零开始重新启动超时计数器。换句话说,如果超时默认是30秒,在脚本运行了了25秒时调用 set_time_limit(20),那么,脚本在超时之前可运行总时间为45秒。

还有一个注意的地方是set_time_limit()函数和配置指令max_execution_time只影响脚本本身执行的时间。任何发生在诸如使用system()的系统调用,流操作,数据库操作等的脚本执行的最大时间不包括其中。同时sleep函数的时间也不包括在其中。

也就是说set_time_limit()函数只针对的是PHP代码本身的执行时间。

例子1:

set_time_limit(5);
sleep(10);
echo "test";

以上代码在10秒后正常的输出test,并且不会报错。
换成下面的代码试试

set_time_limit(5);
for(;;){
   //do something  
}

以上代码执行5秒后会报Fatal error: Maximum execution time of 5 seconds exceeded的错误。

例子2:

set_time_limit(5);
//假设curl需要10秒才能返回数据
$ch = curl_init(); 
curl_setopt($ch, CURLOPT_URL, "http://example.com"); 
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 
$output = curl_exec($ch);  
curl_close($ch);  
var_dump($output);

以上代码在10秒后正常的打印出curl请求返回的结果,并不会在5秒的时候报错。
同理,数据库的相关操作也是如此。

所以,看似一个简单的函数调用,有些细节上的东西还是很有必要了解的。不然坑的可就是自己了?。

mysql中存储emoji表情

在做移动应用时,会遇到ios或者android用户会在文本的区域输入emoji表情(如:??),如果不做一定处理,就会导致插入数据库异常。如下:

Incorrect string value: '\xF0\x9F\x99\x82" ...' for column 'Source'

这是因为emoji表情符号为4个字节的字符,而 utf8 字符集只支持1-3个字节的字符,导致无法写入数据库。

解决方案:

1.修改数据库的字符集为utf8mb4。(注意:mysql版本需要5.5.3以上)具体是需要修改整个mysql库、表或者具体字段字符集自己决定。当然,修改完数据库字符集后还没有大功告成。需要看下程序里连接数据库的时候有没有指定字符集为utf8mb4。php中PDO连接方式如下:

$db = new pdo('mysql:host=127.0.0.1;port=3306;dbname=mysql;charset=utf8mb4','user','password');

2.如果不想修改数据库字符集或者版本比较低不支持,那就只能存的时候转义一下了。可以转为base64格式或者其他支持的转义方式。

 

mongodb保存长整型的问题

最近用php往mongodb里面写数据的时候出现了一点小问题。就是毫秒级的时间戳保存到mongodb里面后数字被截断了。
例如:1439539213873保存后在mongo里显示为:725169713,这个不是我们想要的结果。

php代码:

$mongo = new MongoClient('mongodb://127.0.0.1:27017');
$db = $mongo->selectCollection("local", "test_data");
$data['num'] = 1439539213873;
$res = $db->insert($data);

mongo里结果:

> db.test_data.find();
{ "_id" : ObjectId("56b3036931a08a07628b4567"), "num" : 725169713 }
> 

 

解决方案:

1.查看php中mongo扩展的配置mongo.native_long,如果是0那在php.ini配置里面改为1;

2.对长整型的数字进行转换

$mongo = new MongoClient('mongodb://127.0.0.1:27017');
$db = $mongo->selectCollection("local", "test_data");
//$data['num'] = 1439539213873;
$data['num'] = new MongoInt64('1439539213873');//进行转换
$res = $db->insert($data);

mongo里结果:

> db.test_data.find();
{ "_id" : ObjectId("56b306c431a08ac80f8b4567"), "num" : NumberLong("1439539213873") }
> 

这样长的整型数字就不会被截断了。

记一次php curl给java提交数据失败的经历

最近由于业务需求需要对接一个对外接口(对方为java),简单来说就是需要通过post方式来给对方提交数据。但是对方一直收不到数据。简化代码如下:

$url = 'http://www.test.com';
$data = array(
  'name' => 'test',
  'age' => 123,
); 
$curl = curl_init (); // 启动一个CURL会话
curl_setopt ( $curl, CURLOPT_URL, $url ); // 要访问的地址
curl_setopt ( $curl, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt ( $curl, CURLOPT_POSTFIELDS, $data); // Post提交的数据包
$result = curl_exec ( $curl ); // 执行操作
curl_close ( $curl ); 

后来发现是由于提交数据时header头的content-type跟对方不一致所导致。

  • CURLOPT_POSTFIELDS 这个参数字段数据为一个数组时,Content-Type头将会被设置成multipart/form-data(数据格式);
  • 而这个参数字符串类似’para1=val1&para2=val2&…’时,Content-Type头将会被设置成application/x-www-form-urlencoded(表单格式),就像表单提交的一样。

对方接收的Content-Type为application/x-www-form-urlencoded(表单格式),而现在PHP curl post提交数据时Content-Type为multipart/form-data(数据格式)。这才导致对方无法通过POST来接收数据。现在只需要把代码中

curl_setopt ( $curl, CURLOPT_POSTFIELDS, $data);
//更改为
curl_setopt ( $curl, CURLOPT_POSTFIELDS, http_build_query($data));

就OK了。

在PHP中通过POST方式接收的普通数据直接用$_POST就可以获取到,而如果是二进制流数据需要用$_FILES来获取。如果接收的数据是二进制流数据(文件)还用$_POST来接收,那只获取到的是这个文件的路径名字符串。

另外通过PHP的curl要发送文件,在文件名前面加上@前缀并使用完整路径就可以了。例如:

//$data['file']就是要发送的文件

$ch = curl_init();
$data = array('name' => 'Foo', 'file' => '@/home/user/test.png');
curl_setopt($ch, CURLOPT_URL, 'http://localhost/upload.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_exec($ch);