那些最好的轮子 – PHP篇

 关于不要重复造轮子的二三事一文中,交代了一些背景和想法。本篇则完全是一些干货,列举一些我用过或者即将会用的PHP轮子,基本都符合我对好轮子的定义:开源、许可证宽松、容易集成的PHP项目,目有些已经集成在EvaEngine里面,希望能帮助别人少走弯路。

日志还会陆续补充更新,同时欢迎推荐补充。

 

Databse 数据库ORM

 

Doctrine 2

  • License : MIT
  • Source Code
  • Allo点评:Doctrine是功能最全最完善的PHP orM,社区一直很活跃,对NoSQL也非常迅速的作出了跟进与支持。但之所以没有说Doctrine是最好的,是因为我对PHP究竟有没有必要使用如此庞大的ORM还心存疑虑,平心而论Doctrine的入门门槛实在有些高,尤其是DBAL的提出,更是要把开发者牢牢绑定在Doctrine这艘大船上,用与不用,还是要仔细权衡。

 

RedBeanPHP

  • License : New BSD
  • Source Code
  • Allo点评:相比起Doctrine,RedBean轻巧的简直要飞起来,这两个轮子就是一组最好的比照,是大而全,还是小而精,根据项目选择吧。

 

Documents & Testing 文档与测试

 

phpDocumentor 2

  • License : MIT
  • Source Code
  • Allo点评:老牌php文档生成工具。

 

Faker

  • License : MIT
  • Source Code
  • Allo点评:Faker是一个很神奇的项目,会自动生成拟真的数据,包括用户资料、长文本、IP、日期等等,在网站上线前测试时非常好用。

 

Datetime 时间处理

 

Carbon

  • License : MIT
  • Source Code
  • Allo点评:虽然PHP5内置的Datetime类已经足够应付一般需求,不过Carbon所提供的一些更人性化的处理则更符合实际需求,如果是时间相关的项目应该考虑使用。

 

File System 文件系统

 

Gaufrette

  • License : MIT
  • Source Code
  • Allo点评:文件系统几乎是所有项目都会遇到的问题,Gaufrette为常见的文件系统提供了一套统一接口,包括本地文件/FTP/Dropbox/GridFS/Zip/AmazonS3等等,是大型系统必备的组件。

 

Front-end 前端性能

 

Assetic

  • License : MIT
  • Source Code
  • Allo点评:Assetic可以说生来就是为了多模块的项目而存在的,有了Assetic,可以将分散在各模块中的前端文件编译、合并、压缩。可以让开发人员专注于代码的编写而不是前端文件的生成。

 

lessphp

  • License : MIT
  • Source Code
  • Allo点评:LESS编译器的php版本。不过对于复杂的LESS项目,比如bootstrap,编译的结果与NodeJS原版还是有差异,只能做为Assetic的一个补充。

 

minify

  • License : MIT
  • Source Code
  • Allo点评:PHP版本的CSS/JS压缩器。

 

发布于 分类 网站技术于那些最好的轮子 – PHP篇留下评论

关于不要重复造轮子的二三事

 “不要重复造轮子 Stop Trying to Reinvent the Wheel,可能是每个程序员入行被告知的第一条准则。我自己也会对新人反复灌输这个概念,写程序其实是一个最能“偷懒”的工作:你现在费力实现的每一个功能,可能早已经有极好的解决方法贡献在开源社区,如果可以直接用现成的,那节省下来的时间是不是可以用来偷懒呢?极端的说法,哪怕是那位把所有开发外包给沈阳一家公司的哥们,如果撇开道德以及商业安全,只要能贡献优质的代码和健壮的功能,对于一个项目来说,这样做其实没任何问题。

找轮子存在的问题

虽然不要重复造轮子的准则被反复提到,但是以我个人的经验,这个准则实践起来其实很有难度,因为:

  1. “不要重复造轮子”意味着首先需要找到一个可以用的轮子,而且我们一般希望是能最好的轮子才可以一劳永逸。这就对个人的信息检索能力有非常高的要求。
  2. 找到了一个轮子,但这个轮子好不好用,需要时间来论证。能一眼判断一个项目的质量以及易用性,这其实需要大量项目经验的积累。
  3. 好轮子不是你想用,想用就能用的。要想将一个开源项目整合到自己的项目中,需要对这个项目有比较深的了解。开源项目的文档质量参差不齐,当使用轮子时,只看文档往往是不够的,还需要阅读源代码甚至深度修改定制。更不要说大部分开源项目根本没有中文文档。

所以现实情况往往是:新人不懂得检索方法,找不到轮子;好不容易找到一个轮子,学了半天不会用;好不容易能运行,很多地方与需求不一致,但是又不会改;一来二去,最后还是变成自己写轮子,同时还得出一个结论:别人的轮子都不好用,还是要坚持自己造轮子。

这种情况的最佳体现,就是曾经有一段时间遍地开花的PHP框架。每一个写框架的人都认为自己写的框架才是最好的轮子,甚至是很多PHP新人,对几个成熟框架浅尝辄止后,也纷纷投身写框架的行列。成品大部分看过去却是大同小异,只是语法层面更符合作者本人的习惯,而缺乏大量的测试以及文档社区,最终的结果就是一个半成品然后无疾而终。

这个例子可能有马后炮之嫌,毕竟PHP的造框架运动是由当时的背景和多方面的因素造成的,目前因为有了PHP-FIG制订的规范,PHP的框架的资源已经慢慢集中在Zend FrameworkSymfonyCakePHPYiiCodeIgniter这样少数几个成熟框架之下了。更多的符合PSR规范的模块类库在取代新框架不断涌现,这对整个PHP社区都是好事情。

话题稍微扯的有点远,不过核心的意思还是为了说明,找轮子本身其实是一件不容易的事情。而我对上面问题的解决方法是:找轮子的任务不要交给新人,而是要由经验丰富,信息检索能力强的编程人员负责,最好是项目的构架人员。团队成员找到的轮子最好也由构架人员拍板,用还是不用。对于团队新人,最重要的任务还是编程基本功、文档阅读能力以及如何用好已经拍板的轮子。

怎样才算是好轮子

个人认为好轮子应该具备以下的特征:

  1. 开源,并且License宽松。
  2. 有文档,代码规范,接口友好,最好有实际用例。
  3. 社区相对活跃。
  4. 松耦合,定制容易。

至于同时找到好几个轮子需要选择的情况,可能要根据项目的实际情况进行取舍:有些轮子侧重于大而全,希望解决大部分问题,但是细节上处理不够细致;有些轮子小而精,专注解决一个问题,但是不具备好的通用性。但只要合适好用,都是一个好轮子。

我自己认可的一些好轮子列举在这里:

如何找到好轮子

如何找到好轮子其实在上面问题中已经很清楚了,你应当具备:

  1. 信息检索能力
  2. 外文资料阅读能力
  3. 代码阅读能力以及平时的积累

对于第一条,个人的信息检索能力是无法一蹴而就的,不过如果是还在等待我对此再说出一二三而不是去自己检索寻找方法的朋友,基本上已经没救了。

这里唯一需要说的是,如果想用百度那货去找技术资料的还是省省吧。

外文资料阅读能力也非一日之功,不过个人倒是有个小窍门,如果想要获得一些项目的HelloWorld入门教程或者服务器的配置,可以将检索语言限定为日语,因为日文用户的教程往往秉承了日本细致入微的精神,包括项目背景、需要的环境安装等等一些对初学者才有用的知识,在日文的技术资料中往往也会写的很清楚。

至于平时的积累,可能程序员都知道GithubSourceForgeGoogle Code这些优秀的项目托管网站。但是积累的意思并不是说有时间上去看两眼或者随便收藏一下这么简单。

比如我自己侧重PHP方面的项目,我的一个做法是找到Github下所有Follower大于300的PHP项目(其实一共不到200个其中很多还是php框架),然后一个一个像扫货一样,对其进行了解以及记录。

我的另一个做法是查看知名PHP框架,看看他们用了哪些轮子,比如在Symfony Reference中,你就能淘到Asseticmonolog 这样的好货。

最后一个办法是在Github上Follow一些活跃的作者,比如我的Following中就会显示我的Stars和Forks,里面自然也是我认为值得收藏关注的项目。

PHP NOTICE级错误提示对程序性能影响的研究

我是个有代码洁癖的人,写出来的程序上线后不想见到任何错误 ,而且我也认为错误信息虽然被屏蔽了,但是应该会影响性能的,曾想去验证我这个想法,但还没去验证过,今天正好看到一文章是说这个情况,验证了我的观点,于是摘录如下:

很多人开发php的时候喜欢关掉一部分的错误提示,尤其是Notice级别的提示,这样做可以省去一些严格判断的代码。而平时开发喜欢开着E_ALL级别的我今天在接手同事的项目时,看着满屏幕Notice错误时突然想到了1个问题:

在关掉错误提示时,那么程序代码中大量的notice级别错误是否会造成PHP性能下降,从而使关闭错误输出成为一个掩耳盗铃的做法呢?
下面我来用性能测试来证明这个推论:
测试1:
在关闭错误显示的情况下,测试未初始化变量和已初始化变量在10000000次循环中的性能
变量已初始化的循环代码如下:
PHP代码
  1. $start = getmicrotime();  
  2. error_reporting(E_ALL | E_STRICT);  
  3. ini_set('display_errors', false);  
  4. ini_set('log_errors', false);  
  5. $var=1;  
  6. for ($i=0; $i < 10000000; $i++) {   
  7.     $foo=$var;  
  8. }  
  9. $end = getmicrotime();  
  10. echo $end – $start;  
  11.   
  12. function getmicrotime(){  
  13.     list($usec,$sec)=explode(" ",microtime());  
  14.     return ((float)$usec+(float)$sec);  
  15. }  
变量未初始化的循环代码如下:
PHP代码
  1. $start = getmicrotime();  
  2. error_reporting(E_ALL | E_STRICT);  
  3. ini_set('display_errors', false);  
  4. ini_set('log_errors', false);  
  5.   
  6. for ($i=0; $i < 10000000; $i++) {   
  7.     $foo=$var;  
  8. }  
  9. $end = getmicrotime();  
  10. echo $end – $start;  
  11.   
  12. function getmicrotime(){  
  13.     list($usec,$sec)=explode(" ",microtime());  
  14.     return ((float)$usec+(float)$sec);  
  15. }  
测试成绩:
初始化:平均 5.28 秒
未初始化:平均 17.2 秒
性能差距:3.25倍
 
测试2:
在关闭错误显示的情况下,测试数组索引使用引号和非引号引用在10000000次循环中的性能
数组有引号索引代码如下:
PHP代码
  1. $start = getmicrotime();  
  2. error_reporting(E_ALL | E_STRICT);  
  3. ini_set('display_errors', false);  
  4. ini_set('log_errors', false);  
  5.   
  6. $array=array('foo'=>'baa');  
  7. for ($i=0; $i < 10000000; $i++) {   
  8.     $foo=$array['foo'];  
  9. }  
  10. $end = getmicrotime();  
  11. echo $end – $start;  
  12.   
  13. function getmicrotime(){  
  14.     list($usec,$sec)=explode(" ",microtime());  
  15.     return ((float)$usec+(float)$sec);  
  16. }  
 数组无引号索引代码如下:
PHP代码
  1. $start = getmicrotime();  
  2. error_reporting(E_ALL | E_STRICT);  
  3. ini_set('display_errors', false);  
  4. ini_set('log_errors', false);  
  5.   
  6. $array=array('foo'=>'baa');  
  7. for ($i=0; $i < 10000000; $i++) {   
  8.     $foo=$array[foo];  
  9. }  
  10. $end = getmicrot

面试时,如何向公司提问?

 原文:http://voltsteve.blogspot.com/2011/12/assessing-company-questions-you-need-to.html

原作者:硅谷招聘经理Steve Buckley

  很多人将面试看作一种单向选择,事实上,面试是一种双向选择:不仅是公司挑选你,也是你挑选公司。面试就是为双方提供互相了解的机会,公司在评估你,你也在评估公司。
  面试官也知道这一点,所以他们有心理准备,期待你提出问题,并且会做出回答。所以,面试时不要浪费向公司提问的机会。而且,你主动提问,表明你比较重视这个职位,会加深面试官对你的印象,可能会提高面试的成功率。
 
有一些注意点,你需要知道:
  1. 面试之前,一定要做准备,多了解公司的情况。
  2. 你提出的问题,应该围绕"这份工作是否合适我"这个中心点,其他与应聘关系不大的问题,不宜多问。
  3. 提问的时候,要自然放松,不要害羞,就把它当作普通的聊天。你要表现出对公司的真诚兴趣。
  4. 提问要直接了当,不要绕圈子。提出问题之后,你要保持安静,让面试官多说话。
  5. 面试官回答的时候,你可以做笔记,或者事先询问能不能做。笔记必须简短,你的大部分时间,要用来全神贯注倾听面试官的回答,并与其有眼神的交流。
  6. 面试结束后一周内,最好打一个电话或发一封邮件,了解公司对你的反馈意见。即使面试失败,你不妨也问一下原因,这会有助于你以后的面试。
 
下面是一些你可以问的典型问题。
 
问题一:你们为什么要招聘这个职位?
Q1: Why are you currently recruiting for this position?
这个问题会使得面试官开始谈论当前的项目,或者谈论前一位离职人员。无论哪种情况,都会让你了解,一些与你最密切相关的公司情况。
 
问题二:你们的新员工多吗?
Q2: Do you have many new staffs?
这个问题起一个过渡作用,使得谈话导向公司内部的情况。但是,它本身也能说明一些问题。如果公司成立已经超过四年,又没有新项目,但是新员工却很多,这往往说明公司文化不是很健康。
 
问题三:你们公司(团队)目前面临的最大挑战是什么?
Q3: What are the biggest challenges your team are facing right now?
如果面试官开始谈论一些具体的技术问题,这很好;如果他的回答是项目时间紧迫,或者需要更多的资金,那你就要小心一点了,公司管理上面可能有问题。
 
问题四:什么新技术(编程语言)是你们未来希望采用的?
Q4: What technologies/languages would you like to see your team adapt to that aren't currently being utilised?
如果你申请的是技术职位,面试官恰巧又是技术负责人,那么这个问题将会非常合适。你会对公司的技术路线有所了解和准备,一旦入职,就能更好地适应公司的需要。
 
问题五:在业务方面,有没有什么地方是你们不满意的,未来想要改进的?
Q5: Few companies, if any, are 100% satisfied with the way their business is operating. If you could simply flick a switch to fix it, what one thing would you change?
很少有公司,会百分之百满意自身的现状,即使那些状况很良好的公司也是如此。这个问题可以让你对公司管理层的关注重点和担忧之处,有所了解。
 
问题六:我申请的这个职位,对公司的业务有何影响?
Q6: If you struggle to fill the position I have applied for, what impact would that have on the business?
这个问题会让你了解自己在公司的角色,以及你的岗位对公司是否重要。
(全文完)
 
根据国情及我的经验,我增加几个到人事面那该问的问题,如有不当,请各位指正:
 
1、社保什么时候开始交?
很正规的单位是入职就交,有些单位是转正后补交试用期的(其实这样做是违法),尽量要求公司就交社保,试用期不给交的单位别考虑了。
2、五险一金齐全吗?有没有公积金?
在公积金不是必缴的城市有公积金的单位福利都应该不错。
3、加班怎么处理?
加班按规定算加班工资是最好的,有些单位是调休,连调休都不给的单位就别考虑了(高管岗位不适用,高管本身就有他的特殊性)。
4、发工资按时吗?
相关法规规定工资是要在10号之前发上月的工资,有些单位会稍晚几天,但是只要按时发,不拖欠也还可以接受,经常拖欠的,说明公司财务状况不好,或者不重视员工权益。

 

无限分类的树状迭代方法

 用RecursiveIteratorIterator类来实现,详见代码:

PHP代码
  1. <?php header('Content-Type: text/html; charset=utf-8');?>  
  2.    
  3. <style>  
  4.    
  5.     ul {list-style-type: circle}  
  6.    
  7. </style>  
  8.    
  9. <?php  
  10. $array = array(   
  11.     "苹果" => array (   
  12.         "一代" => array(   
  13.             "苹果1""苹果1S"   
  14.         ),   
  15.         "二代" => array(   
  16.             "苹果2"array(   
  17.             "苹果2 8G","苹果2 16G"   
  18.         ),   
  19.         ),   
  20.         "三代" => array()   
  21.     ),  
  22.     "三星" => array(   
  23.             "galaxy S4""galaxy S5"   
  24.         ),   
  25. );  
  26.    
  27. class RecursiveListIterator extends RecursiveIteratorIterator {   
  28.    
  29.     public $tab = " ";   
  30.    
  31.     public function beginChildren() {   
  32.         if (count($this->getInnerIterator()) == 0) { return; }   
  33.         echo str_repeat($this->tab, $this->getDepth()), "<ul>
    "
    ;   
  34.     }   
  35.    
  36.     public function endChildren() {   
  37.         if (count($this->getInnerIterator()) == 0) { return; }   
  38.         echo str_repeat($this->tab, $this->getDepth()), "</ul>
    "
    ;   
  39.         echo str_repeat($this->tab, $this->getDepth()), "</li>
    "
    ;   
  40.     }   
  41.    
  42.     public function nextElement() {   
  43.         // 显示叶子节点  
  44.         if ( ! $this->callHasChildren()) {   
  45.             echo str_repeat($this->tab, $this->getDepth()+1), '<li>'$this->current(), "</li>
    "
    ;   
  46.             return;   
  47.         }   
  48.    
  49.         // 显示分支标签   
  50.         echo str_repeat($this->tab, $this->getDepth()+1), '<li>'$this->key();   
  51.         echo (count($this->callGetChildren()) == 0) ? "</li>
    "
     : "
    "
    ;   
  52.     }   
  53. }   
  54.    
  55. $it = new RecursiveListIterator(new RecursiveArrayIterator($array), RecursiveIteratorIterator::SELF_FIRST); &

PHP聊天室框架workerman-chat

   workerman-chat是一个以workerman作为服务器容器,使用PHP开发的基于Websocket协议的一个可分布式部署的聊天室框架。

workerman-chat采用gateway workers 进程模型。gateway只负责网络IO,全异步非阻塞,每个gateway进程都可以同时接受上万客户端连接。 workers采用的是PHP开发者所熟悉的同步模型,并提供了开发者基本的接口 onConnect、onMessage、onClose、sendToUid、sendToAll等方法。 开发者只要在onConnect、onMessage、onClose三个方法中添加上自己的业务逻辑即可,开发维护非常简单。

由于采用的是gateway workers 进程模型,gateway和workers之间是无状态的,gateway和workers可以分别部署在不同的物理机上,所以扩容和升级都非常方便。 workerman-chat也非常适合游戏后台开发。

查看php聊天室demo请点击这里

特性

  • 使用PHP开发
  • PHP多进程
  • gateway workers进程模型
  • 支持libevent事件轮询库,支持高并发
  • 默认使用Websocket协议,更小带宽,更好性能
  • 支持分布式部署,可横向扩容
  • 客户端跨浏览器支持(需要浏览器支持html5或者flash)
  • 同样非常适合游戏后台开发

安装启动

1、下载workerman-chat,并解压缩到任意目录

2、启动workerman./bin/workermand start如下图

php聊天室启动示意图

3、浏览器访问端口55151,例如workerman.net:55151如图:

php聊天室使用界面

说明

本聊天室业务逻辑非常简单,业务逻辑都在文件./applications/Chat/Event.php中,开发者可以随意修改,比如增加私聊、表情、分组等功能

解决phpmyadmin查看表结构一直在加载的问题

   今天这两天用电脑上原来安装好的xampp上带的phpMyadmin来管理Mysql数据库,查看表结构时一直显示“正在加载”,从Google上百度后得到了解决方法,见:http://www.fenanr.com/read/112032.html

我主要用到了下面这些:
 
打开 ./libraries/Util.class.php 文件.
查找:
PHP代码
  1. return strftime($date$timestamp);  

替换成如下代码:

PHP代码
  1. if(extension_loaded('gettext'))   
  2.   return strftime($date$timestamp);  

中国区可以替换成以下代码:

PHP代码
  1. if(extension_loaded('gettext')){           
  2. date_default_timezone_set('UTC');   
  3. return gmdate('Y-m-d H:i:s'$timestamp + 28800);}  

作者提到:原理: 本地化时间格式化需要gettext支持, 假如你的环境没有开启此功能, 将会返回乱码, 影响#phpmyadmin ajax的处理. 

我这发现开启了gettext依旧会乱码,所以前部分代码用strftime不起作用,后面这个用gmdate才行。但是没必要判断gettext了吧。

搜索了一圈,没有找到乱码的解决方案,说是strftime对中文的支持不好,放弃了,用其它方法来格式化时间吧,比如date等函数。

xampp套件Apache启动失败解决方案

   刚换了工作,今天第一天报到,熟悉环境,配置开发环境。给我分配的工作电脑上已经安装有XAMPP,发现Apache启动不了,XAMPP控制面板上提示:

XML/HTML代码
  1. 11:27:36  [Apache]  Error: Apache shutdown unexpectedly.  
  2. 11:27:36  [Apache]  This may be due to a blocked port, missing dependencies,   
  3. 11:27:36  [Apache]  improper privileges, a crash, or a shutdown by another method.  
  4. 11:27:36  [Apache]  Press the Logs button to view error logs and check  
  5. 11:27:36  [Apache]  the Windows Event Viewer for more clues  
  6. 11:27:36  [Apache]  If you need more help, copy and post this  
  7. 11:27:36  [Apache]  entire log window on the forums  

  看这提示信息,应该是端口被占用,用DOS命令netstat -ano查了端口,也没发现有被占用的。用命令行模式到D:xamppapachein目录下运行httpd,提示:

XML/HTML代码
  1. AH00526: Syntax error on line 238 of D:/xampp/apache/conf/httpd.conf:  
  2. DocumentRoot must be a directory  

打开配置文件,发现DocumentRoot设置的目录已经不存在了,估计是以前用这台电脑的同事清理了,改为一个存在的目录,再重启Apache,成功了。

常用的PHP类库—PHP开发者必备

PDF 生成器

FPDF – 这量一个可以让你生成PDF的纯PHP类库。

Excel 相关

你的站点需要生成Excel?没有问题,下面这两个类库可以让你轻松做到这一点。

php-excel – 这是一个非常简单的Excel文件生成类。(用PHPExcel读取excel并导入数据库

PHP Excel Reader – 可以解析并读取XLS文件中的数据。

扩展阅读:

php-excel-reader读取excel内容存入数据库

使用php-excel-reader读取excel文件

E-Mail 相关

不喜欢PHP的mail函数?觉得不够强大?下面的PHP邮件相关的库绝对不会让你失望。

Swift Mailer – 免费的超多功能的PHP邮件库。

PHPMailer – 超强大的邮件发送类。(PHPMailer使用教程,使用PHPMailer发送邮件(含附件下载)

单元测试

如果你在使用测试驱动的方法开发你的程序,下面的类库和框架绝你能帮助你的开发。

SimpleTest – 一个PHP的单元测试和网页测试的框架。

PHPUnit – 来自xUnit 家族,提供一个框架可以让你方便地进行单元测试的案例开发。并可非常容易地分析其测试结果。

图表库

下面的类库可以让你很简的创建复杂的图表和图片。当然,它们需要GD库的支持。

pChart – 一个可以创建统计图的库。

Libchart – 这也是一个简单的统计图库。

JpGraph – 一个面向对象的图片创建类。jpGraph的应用及基本安装配置

Open Flash Chart – 这是一个基于Flash的统计图。

RSS 解析

解释RSS并是一件很单调的事情,不过幸好你有下面的类库可以帮助你方便地读取RSS的Feed。

MagpieRSS – 开源的PHP版RSS解析器,据说功能强大,未验证。

SimplePie – 这是一个非常快速,而且易用的RSS和Atom 解析库。

缩略图生成

phpThumb – 功能很强大,如何强大还是自己去体会吧。

支付

你的网站需要处理支付方面的事情?需要一个和支付网关的程序?下面这个程序可以帮到你。

PHP Payment Library – 支持Paypal, Authorize.net 和2Checkout (2CO)

OpenID

PHP-OpenID – 支持OpenID的一个PHP库。OpenID是帮助你使用相同的用户名和口令登录不同的网站的一种解决方案。如果你对OpenID不熟悉的话,你可以到这里看看:http://openid.net.cn/

对象关系映射ORM

ADOdb – 数据库抽象(adodb专题教程

Doctrine – 对象关系映射Object relational mapper (ORM) ,需要 PHP 5.2.3+ 版本,一个非常强大的database abstraction layer (DBAL).

Propel – 对象关系映射框架- PHP5

Outlet – 也是关于对象关系映射的一个工具。

本文转载自:phpddt

PHP中该怎样防止SQL注入?

本文由 伯乐在线 – rokety 翻译自 StackOverflow。欢迎加入技术翻译小组

 如果用户输入的数据在未经处理的情况下插入到一条SQL查询语句,那么应用将很可能遭受到SQL注入攻击,正如下面的例子:

PHP代码
  1. $unsafe_variable = $_POST['user_input'];    
  2. mysql_query("Insert INTO `table` (`column`) VALUES ('" . $unsafe_variable . "')");  
因为用户的输入可能是这样的:
 
value'); Drop TABLE table;–
那么SQL查询将变成如下:
 
Insert INTO `table` (`column`) VALUES('value'); Drop TABLE table;–')
应该采取哪些有效的方法来防止SQL注入?
 
 
最佳回答(来自Theo):
 
使用预处理语句和参数化查询。预处理语句和参数分别发送到数据库服务器进行解析,参数将会被当作普通字符处理。这种方式使得攻击者无法注入恶意的SQL。 你有两种选择来实现该方法:
 
1、使用PDO:
PHP代码
  1. $stmt = $pdo->prepare('Select * FROM employees Where name = :name');   
  2. $stmt->execute(array('name' => $name));     
  3. foreach ($stmt as $row) {  
  4.     // do something with $row  
  5. }

2、使用mysqli:

PHP代码
  1. $stmt = $dbConnection->prepare('Select * FROM employees Where name = ?');  
  2. $stmt->bind_param('s'$name);  
  3. $stmt->execute();  
  4. $result = $stmt->get_result();  
  5. while ($row = $result->fetch_assoc()) {  
  6.     // do something with $row  
  7. }  

注意,在默认情况使用PDO并没有让MySQL数据库执行真正的预处理语句(原因见下文)。为了解决这个问题,你应该禁止PDO模拟预处理语句。一个正确使用PDO创建数据库连接的例子如下:

PHP代码
  1. $dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8''user''pass');  
  2. $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);  
  3. $dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);  
在上面的例子中,报错模式(ATTR_ERRMODE)并不是必须的,但建议加上它。这样,当发生致命错误(Fatal Error)时,脚本就不会停止运行,而是给了程序员一个捕获PDOExceptions的机会,以便对错误进行妥善处理。 然而,第一个setAttribute()调用是必须的,它禁止PDO模拟预处理语句,而使用真正的预处理语句,即有MySQL执行预处理语句。这能确保语句和参数在发送给MySQL之前没有被PHP处理过,这将使得攻击者无法注入恶意SQL。了解原因,可参考这篇博文:PDO防注入原理分析以及使用PDO的注意事项。 注意在老版本的PHP(<5.3.6),你无法通过在PDO的构造器的DSN上设置字符集,参考:silently ignored the charset parameter。
 
解析
 
当你将SQL语句发送给数据库服务器进行预处理和解析时发生了什么?通过指定占位符(一个?或者一个上面例子中命名的 :name),告诉数据库引擎你想在哪里进行过滤。当你调用execute的时候,预处理语句将会与你指定的参数值结合。 关键点就在这里:参数的值是和经过解析的SQL语句结合到一起,而不是SQL字符串。SQL注入是通过触发脚本在构造SQL语句时包含恶意的字符串。所以,通过将SQL语句和参数分开,你防止了SQL注入的风险。任何你发送的参数的值都将被当作普通字符串,而不会被数据库服务器解析。回到上面的例子,如果$name变量的值为 ’Sarah’; Delete FROM employees ,那么实际的查询将是在 employees 中查找 name 字段值为 ’Sarah’; Delete FROM employees 的记录。 另一个使用预处理语句的好处是:如果你在同一次数据库连接会话中执行同样的语句许多次,它将只被解析一次,这可以提升一点执行速度。 如果你想问插入该如何做,请看下面这个例子(使用PDO):
PHP代码
  1. $preparedStatement = $db->prepare('Insert INTO table (column) VALUES (:column)');  
  2. $preparedStatement->execute(array('column' => $unsafeValue));