标签归档:PHP

PHP 坑之 ==

PHP 中 “== ”判断两个变量是否相等的时候会进行自动的类型转换。今天Tom Hessman,在twitter上提到了PHP用==做判断的时候一种极端情况(很坑)。

1
2
3
4
5
6
//ok it return true
var_dump(md5('240610708') == md5('QNKCDZO'));
var_dump(md5('240610708'));
//string(32) "0e462097431906509019562988736854"
var_dump(md5('QNKCDZO'));
string(32) "0e830400451993494058024219903391"

PHP把两个字符串转换成科学计数来比较了。。。

严谨比较还是用 === 这个吧

PHP 15 年提交历史可视化展示

Gource 是一个开源的用来做软件版本修改可视化的工作,下面的视频就是用Gource做的PHP 15 年提交历史可视化展示。之所以会提到这个是因为最近半年一直在和数据统计打交道,估计2014大部分时间可能也是做这项工作。其实和数据打交道也蛮好玩的,纯粹!

上面是我上传的优酷的版本,不是很清晰,有条件的可以看下https://www.youtube.com/watch?v=pYjs4DSxgbU

PS:晚上的火车,明天就可以到家了,祝大家新年快乐,马上啥都有~

PHP中feof函数有可能造成死循环

前段时间公司的一个页面经常报告超时,本来以为是发邮件导致的超时。后来把邮件发送拆成异步之后,超时的问题并没有解决。于是仔细研究了下超时附近的代码段。

发现有类似一下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
<?php

$file = fopen("contacts.csv","r");

while(! feof($file))
{
  print_r(fgetcsv($file));
}

fclose($file);

?>

其实上面的代码就是W3scholl的示例代码

如果没有仔细看过PHP的手册,大概看不出这段代码有什么问题?

问题的关键就在feof这个函数,手册上有明确的说明:

If the passed file pointer is not valid you may get an infinite loop, because feof() fails to return TRUE.

详见:http://cn2.php.net/feof

所以上面的代码如果打开的文件失败,就会导致死循环,从而引起页面超时。

用JS写的PHP VM(让PHP跑着JS上)

前段时间有个Javasccript Linux让我们看到了Javascript的魅力,前天在twitter看到@Niklasvh 写了一个PHPJS,可以让PHP跑在JS上,很久就期待有这样的一种实现,可以让我在浏览器里运行PHP代码,现在她终于来了,感谢@Niklasvh

项目主页:http://phpjs.hertzen.com/

我在SAE上也搭了一个:http://mytools.sinaapp.com/phpjs/

在什么场景下使用闭包?

在需要把逻辑封装到自己的范围内的情况下,闭包会十分有用。重构旧代码以进行简化并提高可读性就是这样一个例子。查看以下示例,该示例显示了在运行一些 SQL 查询时使用的记录程序。

1
2
3
4
5
6
7
8
9
10
//记录 SQL 查询的代码               
$db = mysqli_connect("server","user","pass");
Logger::log('debug','database','Connected to database');
$db->query('insert into parts (part, description) values ('Hammer','Pounds nails');
Logger::log('
debug','database','Insert Hammer into to parts table');
$db->query('
insert into parts (part, description) values
      ('Drill','Puts holes in wood');
Logger::log('debug','database','Insert Drill into to parts table');
$db->query('insert into parts (part, description) values ('Saw','Cuts wood');
Logger::log('
debug','database','Insert Saw into to parts table');

可以看出执行操作的重复程度。对 Logger::log() 执行的每次调用都有相同的前两个实参。为了解决此问题,我们可以把该方法调用放入闭包并转而针对该闭包执行调用。得到的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
//记录 SQL 查询的重构代码             
$logdb = function ($string) { Logger::log('debug','database',$string); };
$db = mysqli_connect("server","user","pass");
$logdb('Connected to database');
$db->query('insert into parts (part, description) values ('Hammer','Pounds nails');
$logdb('
Insert Hammer into to parts table');
$db->query('
insert into parts (part, description) values
       ('Drill','Puts holes in wood');
$logdb('Insert Drill into to parts table');
$db->query('insert into parts (part, description) values ('Saw','Cuts wood');
$logdb('
Insert Saw into to parts table');

整理自:http://www.ibm.com/developerworks/cn/opensource/os-php-5.3new2/
PS:原文有些不正确的地方,比方说到PHP 5.3.5都不可以通过闭包中的this指针访问类内的成员变量(当然可以通过其他方法实现),文章大部分内容正确,还是“尽信书不如无书”。

【转载】浅谈PHP中的Session机制

做web开发,必然会涉及到Session,这是由于http协议本身是无状态的(每次响应都是独立的,彼此间没有联系),所以如果需要在页面跳转间保持某个用户的身份,就要在每次连接时告诉服务器端你的唯一标示号,即Session ID。这样,服务器端便可通过Session ID得到所需的数据。

在PHP中,Session是通过$_SESSION这个全局变量来set/get的,不过在使用之前要先初始化。初始化是通过session_start函数(如果php.ini中将session.auto_start设为1,则会自动初始化),之后PHP会为request自动生成一个唯一随机数作为Session ID,生成算法默认提供了MD5 (128 bits) 和SHA-1 (160 bits),由php.ini中session.hash_function设定。其实也可以自定义,比如在随机数基础上将来访者的IP地址也加入到算法中,像CodeIgniter1.7.2中代码:

1
2
3
4
5
6
7
8
$sessid = '';
while (strlen($sessid) < 32)
{
    $sessid .= mt_rand(0, mt_getrandmax());
}
// To make the session ID even more secure we'll combine it with the user's IP
$sessid .= $this->CI->input->ip_address();
$sessid = md5(uniqid($sessid, TRUE))

生成的ID存放在服务器的某一目录下,这由php.ini中session.save_path配置。如果你选择默认的文件式session存储,那么可能会遇到大量session文件导致IO性能下降,这个问题可以通过调节save_path来优化,具体请看大量php session临时文件带来的服务器效率问题。如果要在多个服务器中同步session id,你可以将其存放在数据库或共享缓存中。这需要你自定义一系列Session的读写方法,并在调用session_start函数前先设定好,以下面代码为例(来自php document中的一段示例代码):

1
2
3
4
5
6
CREATE TABLE `ws_sessions` (
  `session_id` VARCHAR(255) BINARY NOT NULL DEFAULT '',
  `session_expires` INT(10) UNSIGNED NOT NULL DEFAULT '0',
  `session_data` text,
  PRIMARY KEY  (`session_id`)
) TYPE=InnoDB;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
<?php
class session {
   // session-lifetime
   var $lifeTime;
   // mysql-handle
   var $dbHandle;
   function open($savePath, $sessName) {
       // get session-lifetime
       $this->lifeTime = get_cfg_var("session.gc_maxlifetime");
       // open database-connection
       $dbHandle = @mysql_connect("server","user","password");
       $dbSel = @mysql_select_db("database",$dbHandle);
       // return success
       if(!$dbHandle || !$dbSel)
           return false;
       $this->dbHandle = $dbHandle;
       return true;
   }
   function close() {
       $this->gc(ini_get('session.gc_maxlifetime'));
       // close database-connection
       return @mysql_close($this->dbHandle);
   }
   function read($sessID) {
       // fetch session-data
       $res = mysql_query("SELECT session_data AS d FROM ws_sessions
                           WHERE session_id = '$sessID'
                           AND session_expires > "
.time(),$this->dbHandle);
       // return data or an empty string at failure
       if($row = mysql_fetch_assoc($res))
           return $row['d'];
       return "";
   }
   function write($sessID,$sessData) {
       // new session-expire-time
       $newExp = time() + $this->lifeTime;
       // is a session with this id in the database?
       $res = mysql_query("SELECT * FROM ws_sessions
                           WHERE session_id = '$sessID'"
,$this->dbHandle);
       // if yes,
       if(mysql_num_rows($res)) {
           // ...update session-data
           mysql_query("UPDATE ws_sessions
                         SET session_expires = '$newExp',
                         session_data = '$sessData'
                         WHERE session_id = '$sessID'"
,$this->dbHandle);
           // if something happened, return true
           if(mysql_affected_rows($this->dbHandle))
               return true;
       }
       // if no session-data was found,
       else {
           // create a new row
           mysql_query("INSERT INTO ws_sessions (
                         session_id,
                         session_expires,
                         session_data)
                         VALUES(
                         '$sessID',
                         '$newExp',
                         '$sessData')"
,$this->dbHandle);
           // if row was created, return true
           if(mysql_affected_rows($this->dbHandle))
               return true;
       }
       // an unknown error occured
       return false;
   }
   function destroy($sessID) {
       // delete session-data
       mysql_query("DELETE FROM ws_sessions WHERE session_id = '$sessID'",$this->dbHandle);
       // if session was deleted, return true,
       if(mysql_affected_rows($this->dbHandle))
           return true;
       // ...else return false
       return false;
   }
   function gc($sessMaxLifeTime) {
       // delete old sessions
       mysql_query("DELETE FROM ws_sessions WHERE session_expires < ".time(),$this->dbHandle);
       // return affected rows
       return mysql_affected_rows($this->dbHandle);
   }
}
$session = new session();
session_set_save_handler(array(&$session,"open"),
                         array(&$session,"close"),
                         array(&$session,"read"),
                         array(&$session,"write"),
                         array(&$session,"destroy"),
                         array(&$session,"gc"));
session_start();
// etc...
?>

除了上述方法外还有其他办法可以保持Session的同步,可以参考PHP SESSION解惑一文中第四部分“session的同步”。

下面再谈谈Session ID的传递方式:Cookie和URL传递。
Cookie是比较常用的方式,在这种模式下,启动Session后服务器会在HTTP Response中自动加上header(‘Set-Cookie: session_name()=session_id(); path=/’),并在以后的请求中加上这个Cookie。当从该页跳转到的新页面并调用session_start()后,PHP将检查与给定ID相关联的服务器端存贮的session数据,如果没找到,则新建一个数据集。但是有一点,这种传递方式必须在用户浏览器开启Cookie的情况下才可用,如果万一用户关闭了Cookie,那么只好选择另外一种通过URL参数传递Session ID。
开启URL传递需要在php.ini中设置session.use_trans_sid(文档中提示使用这种方式会有安全风险,因为它显示地将Session ID放在url中,所以除非迫不得已不要选择此方式),并在代码中做如下修改:

1
2
3
4
5
6
// 如果客户端使用cookie,可直接传递session到page2.php

echo '<br /><a href="page2.php">page 2</a>';
 
// 如果客户端禁用cookie
echo '<br /><a href="page2.php?' . SID . '">page 2</a>';

/*
默认php5.2.1下,SID只有在cookie被写入的同时才会有值,如果该session
对应的cookie已经存在,那么SID将为(未定义)空
*/
更新2010-12-28:
如果php使用默认的file方式存储session,还要注意lock问题。因为php会lock住session文件直到这个session关闭,所以如果你的应用中涉及iframe、下载、Comet或者用户在同一个浏览器打开多个tab等等多个并行请求都要操作session时,就可能会遇到由于lock影响用户操作的情况。一个简单的解决办法就是在操作完session时,及时调用session_commit()或session_write_close()来关闭session,从而释放锁。(注意在关闭session后不要再调用任何session相关的函数)

关于Session的内容还有很多,具体可查看官方手册,如果有新的总结会继续更新。
原文链接:http://imdonkey.com/blog/archives/255