久久久久久久视色,久久电影免费精品,中文亚洲欧美乱码在线观看,在线免费播放AV片

<center id="vfaef"><input id="vfaef"><table id="vfaef"></table></input></center>

    <p id="vfaef"><kbd id="vfaef"></kbd></p>

    
    
    <pre id="vfaef"><u id="vfaef"></u></pre>

      <thead id="vfaef"><input id="vfaef"></input></thead>

    1. 站長(zhǎng)資訊網(wǎng)
      最全最豐富的資訊網(wǎng)站

      PHP程序員容易犯的10個(gè)錯(cuò)誤(分享)

      本篇文章給大家介紹10個(gè) PHP 開發(fā)者最容易犯的錯(cuò)誤。有一定的參考價(jià)值,有需要的朋友可以參考一下,希望對(duì)大家有所幫助。

      PHP程序員容易犯的10個(gè)錯(cuò)誤(分享)

      PHP 語(yǔ)言讓 WEB 端程序設(shè)計(jì)變得簡(jiǎn)單,這也是它能流行起來(lái)的原因。但也是因?yàn)樗暮?jiǎn)單,PHP 也慢慢發(fā)展成一個(gè)相對(duì)復(fù)雜的語(yǔ)言,層出不窮的框架,各種語(yǔ)言特性和版本差異都時(shí)常讓搞的我們頭大,不得不浪費(fèi)大量時(shí)間去調(diào)試。這篇文章列出了十個(gè)最容易出錯(cuò)的地方,值得我們?nèi)プ⒁狻?/p>

      易犯錯(cuò)誤 #1: 在 foreach循環(huán)后留下數(shù)組的引用

      還不清楚 PHP 中 foreach 遍歷的工作原理?如果你在想遍歷數(shù)組時(shí)操作數(shù)組中每個(gè)元素,在 foreach 循環(huán)中使用引用會(huì)十分方便,例如

      $arr = array(1, 2, 3, 4); foreach ($arr as &$value) {         $value = $value * 2; } // $arr 現(xiàn)在是 array(2, 4, 6, 8)

      問(wèn)題是,如果你不注意的話這會(huì)導(dǎo)致一些意想不到的負(fù)面作用。在上述例子,在代碼執(zhí)行完以后,$value 仍保留在作用域內(nèi),并保留著對(duì)數(shù)組最后一個(gè)元素的引用。之后與 $value 相關(guān)的操作會(huì)無(wú)意中修改數(shù)組中最后一個(gè)元素的值。

      你要記住 foreach 并不會(huì)產(chǎn)生一個(gè)塊級(jí)作用域。因此,在上面例子中 $value 是一個(gè)全局引用變量。在 foreach 遍歷中,每一次迭代都會(huì)形成一個(gè)對(duì) $arr 下一個(gè)元素的引用。當(dāng)遍歷結(jié)束后, $value 會(huì)引用 $arr 的最后一個(gè)元素,并保留在作用域中

      這種行為會(huì)導(dǎo)致一些不易發(fā)現(xiàn)的,令人困惑的bug,以下是一個(gè)例子

      $array = [1, 2, 3]; echo implode(',', $array), "n";  foreach ($array as &$value) {}    // 通過(guò)引用遍歷 echo implode(',', $array), "n";  foreach ($array as $value) {}     // 通過(guò)賦值遍歷 echo implode(',', $array), "n";

      以上代碼會(huì)輸出

      1,2,3 1,2,3 1,2,2

      你沒(méi)有看錯(cuò),最后一行的最后一個(gè)值是 2 ,而不是 3 ,為什么?

      在完成第一個(gè) foreach 遍歷后, $array 并沒(méi)有改變,但是像上述解釋的那樣, $value 留下了一個(gè)對(duì) $array 最后一個(gè)元素的危險(xiǎn)的引用(因?yàn)?foreach 通過(guò)引用獲得 $value

      這導(dǎo)致當(dāng)運(yùn)行到第二個(gè) foreach ,這個(gè)"奇怪的東西"發(fā)生了。當(dāng) $value 通過(guò)賦值獲得, foreach 按順序復(fù)制每個(gè) $array 的元素到 $value 時(shí),第二個(gè) foreach 里面的細(xì)節(jié)是這樣的

      • 第一步:復(fù)制 $array[0] (也就是 1 )到 $value$value 其實(shí)是 $array最后一個(gè)元素的引用,即 $array[2]),所以 $array[2] 現(xiàn)在等于 1。所以 $array 現(xiàn)在包含 [1, 2, 1]
      • 第二步:復(fù)制 $array[1](也就是 2 )到 $value$array[2] 的引用),所以 $array[2] 現(xiàn)在等于 2。所以 $array 現(xiàn)在包含 [1, 2, 2]
      • 第三步:復(fù)制 $array[2](現(xiàn)在等于 2 ) 到 $value$array[2] 的引用),所以 $array[2] 現(xiàn)在等于 2 。所以 $array 現(xiàn)在包含 [1, 2, 2]

      為了在 foreach 中方便的使用引用而免遭這種麻煩,請(qǐng)?jiān)?foreach 執(zhí)行完畢后 unset() 掉這個(gè)保留著引用的變量。例如

      $arr = array(1, 2, 3, 4); foreach ($arr as &$value) {     $value = $value * 2; } unset($value);   // $value 不再引用 $arr[3]

      常見錯(cuò)誤 #2: 誤解 isset() 的行為

      盡管名字叫 isset,但是 isset() 不僅會(huì)在變量不存在的時(shí)候返回 false,在變量值為 null 的時(shí)候也會(huì)返回 false。

      這種行為比最初出現(xiàn)的問(wèn)題更為棘手,同時(shí)也是一種常見的錯(cuò)誤源。

      看看下面的代碼:

      $data = fetchRecordFromStorage($storage, $identifier); if (!isset($data['keyShouldBeSet']) {     // do something here if 'keyShouldBeSet' is not set }

      開發(fā)者想必是想確認(rèn) keyShouldBeSet 是否存在于 $data 中。然而,正如上面說(shuō)的,如果 $data['keyShouldBeSet'] 存在并且值為 null 的時(shí)候, isset($data['keyShouldBeSet']) 也會(huì)返回 false。所以上面的邏輯是不嚴(yán)謹(jǐn)?shù)摹?/p>

      我們來(lái)看另外一個(gè)例子:

      if ($_POST['active']) {     $postData = extractSomething($_POST); }  // ...  if (!isset($postData)) {     echo 'post not active'; }

      上述代碼,通常認(rèn)為,假如 $_POST['active'] 返回 true,那么 postData 必將存在,因此 isset($postData) 也將返回 true。反之, isset($postData) 返回 false 的唯一可能是 $_POST['active'] 也返回 false

      然而事實(shí)并非如此!

      如我所言,如果$postData 存在且被設(shè)置為 null, isset($postData) 也會(huì)返回 false 。 也就是說(shuō),即使 $_POST['active'] 返回 true, isset($postData) 也可能會(huì)返回 false 。 再一次說(shuō)明上面的邏輯不嚴(yán)謹(jǐn)。

      順便一提,如果上面代碼的意圖真的是再次確認(rèn) $_POST['active'] 是否返回 true,依賴 isset() 來(lái)做,不管對(duì)于哪種場(chǎng)景來(lái)說(shuō)都是一種糟糕的決定。更好的做法是再次檢查 $_POST['active'],即:

      if ($_POST['active']) {     $postData = extractSomething($_POST); }  // ...  if ($_POST['active']) {     echo 'post not active'; }

      對(duì)于這種情況,雖然檢查一個(gè)變量是否真的存在很重要(即:區(qū)分一個(gè)變量是未被設(shè)置還是被設(shè)置為 null);但是使用 array_key_exists() 這個(gè)函數(shù)卻是個(gè)更健壯的解決途徑。

      比如,我們可以像下面這樣重寫上面第一個(gè)例子:

      $data = fetchRecordFromStorage($storage, $identifier); if (! array_key_exists('keyShouldBeSet', $data)) {     // do this if 'keyShouldBeSet' isn't set }

      另外,通過(guò)結(jié)合 array_key_exists()get_defined_vars(), 我們能更加可靠的判斷一個(gè)變量在當(dāng)前作用域中是否存在:

      if (array_key_exists('varShouldBeSet', get_defined_vars())) {     // variable $varShouldBeSet exists in current scope }

      常見錯(cuò)誤 #3:關(guān)于通過(guò)引用返回與通過(guò)值返回的困惑

      考慮下面的代碼片段:

      class Config {     private $values = [];      public function getValues() {         return $this->values;     } }  $config = new Config();  $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];

      如果你運(yùn)行上面的代碼,將得到下面的輸出:

      PHP Notice:  Undefined index: test in /path/to/my/script.php on line 21

      出了什么問(wèn)題?

      上面代碼的問(wèn)題在于沒(méi)有搞清楚通過(guò)引用與通過(guò)值返回?cái)?shù)組的區(qū)別。除非你明確告訴 PHP 通過(guò)引用返回一個(gè)數(shù)組(例如,使用 &),否則 PHP 默認(rèn)將會(huì)「通過(guò)值」返回這個(gè)數(shù)組。這意味著這個(gè)數(shù)組的一份拷貝將會(huì)被返回,因此被調(diào)函數(shù)與調(diào)用者所訪問(wèn)的數(shù)組并不是同樣的數(shù)組實(shí)例。

      所以上面對(duì) getValues() 的調(diào)用將會(huì)返回 $values 數(shù)組的一份拷貝,而不是對(duì)它的引用??紤]到這一點(diǎn),讓我們重新回顧一下以上例子中的兩個(gè)關(guān)鍵行:

      // getValues() 返回了一個(gè) $values 數(shù)組的拷貝 // 所以`test`元素被添加到了這個(gè)拷貝中,而不是 $values 數(shù)組本身。 $config->getValues()['test'] = 'test';   // getValues() 又返回了另一份 $values 數(shù)組的拷貝 // 且這份拷貝中并不包含一個(gè)`test`元素(這就是為什么我們會(huì)得到 「未定義索引」 消息)。 echo $config->getValues()['test'];

      一個(gè)可能的修改方法是存儲(chǔ)第一次通過(guò) getValues() 返回的 $values 數(shù)組拷貝,然后后續(xù)操作都在那份拷貝上進(jìn)行;例如:

      $vals = $config->getValues(); $vals['test'] = 'test'; echo $vals['test'];

      這段代碼將會(huì)正常工作(例如,它將會(huì)輸出test而不會(huì)產(chǎn)生任何「未定義索引」消息),但是這個(gè)方法可能并不能滿足你的需求。特別是上面的代碼并不會(huì)修改原始的$values數(shù)組。如果你想要修改原始的數(shù)組(例如添加一個(gè)test元素),就需要修改getValues()函數(shù),讓它返回一個(gè)$values數(shù)組自身的引用。通過(guò)在函數(shù)名前面添加一個(gè)&來(lái)說(shuō)明這個(gè)函數(shù)將返回一個(gè)引用;例如:

      class Config {     private $values = [];      // 返回一個(gè) $values 數(shù)組的引用     public function &getValues() {         return $this->values;     } }  $config = new Config();  $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];

      這會(huì)輸出期待的test

      但是現(xiàn)在讓事情更困惑一些,請(qǐng)考慮下面的代碼片段:

      class Config {     private $values;      // 使用數(shù)組對(duì)象而不是數(shù)組     public function __construct() {         $this->values = new ArrayObject();     }      public function getValues() {         return $this->values;     } }  $config = new Config();  $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];

      如果你認(rèn)為這段代碼會(huì)導(dǎo)致與之前的數(shù)組例子一樣的「未定義索引」錯(cuò)誤,那就錯(cuò)了。實(shí)際上,這段代碼將會(huì)正常運(yùn)行。原因是,與數(shù)組不同,PHP 永遠(yuǎn)會(huì)將對(duì)象按引用傳遞。(ArrayObject 是一個(gè) SPL 對(duì)象,它完全模仿數(shù)組的用法,但是卻是以對(duì)象來(lái)工作。)

      像以上例子說(shuō)明的,你應(yīng)該以引用還是拷貝來(lái)處理通常不是很明顯就能看出來(lái)。因此,理解這些默認(rèn)的行為(例如,變量和數(shù)組以值傳遞;對(duì)象以引用傳遞)并且仔細(xì)查看你將要調(diào)用的函數(shù) API 文檔,看看它是返回一個(gè)值,數(shù)組的拷貝,數(shù)組的引用或是對(duì)象的引用是必要的。

      盡管如此,我們要認(rèn)識(shí)到應(yīng)該盡量避免返回一個(gè)數(shù)組或 ArrayObject,因?yàn)檫@會(huì)讓調(diào)用者能夠修改實(shí)例對(duì)象的私有數(shù)據(jù)。這就破壞了對(duì)象的封裝性。所以最好的方式是使用傳統(tǒng)的「getters」和「setters」,例如:

      class Config {     private $values = [];      public function setValue($key, $value) {         $this->values[$key] = $value;     }      public function getValue($key) {         return $this->values[$key];     } }  $config = new Config();  $config->setValue('testKey', 'testValue'); echo $config->getValue('testKey');    // 輸出『testValue』

      這個(gè)方法讓調(diào)用者可以在不對(duì)私有的$values數(shù)組本身進(jìn)行公開訪問(wèn)的情況下設(shè)置或者獲取數(shù)組中的任意值。

      常見的錯(cuò)誤 #4:在循環(huán)中執(zhí)行查詢

      如果像這樣的話,一定不難見到你的 PHP 無(wú)法正常工作。

      $models = [];  foreach ($inputValues as $inputValue) {     $models[] = $valueRepository->findByValue($inputValue); }

      這里也許沒(méi)有真正的錯(cuò)誤, 但是如果你跟隨著代碼的邏輯走下去, 你也許會(huì)發(fā)現(xiàn)這個(gè)看似無(wú)害的調(diào)用$valueRepository->findByValue() 最終執(zhí)行了這樣一種查詢,例如:

      $result = $connection->query("SELECT `x`,`y` FROM `values` WHERE `value`=" . $inputValue);

      結(jié)果每輪循環(huán)都會(huì)產(chǎn)生一次對(duì)數(shù)據(jù)庫(kù)的查詢。 因此,假如你為這個(gè)循環(huán)提供了一個(gè)包含 1000 個(gè)值的數(shù)組,它會(huì)對(duì)資源產(chǎn)生 1000 單獨(dú)的請(qǐng)求!如果這樣的腳本在多個(gè)線程中被調(diào)用,他會(huì)有導(dǎo)致系統(tǒng)崩潰的潛在危險(xiǎn)。

      因此,至關(guān)重要的是,當(dāng)你的代碼要進(jìn)行查詢時(shí),應(yīng)該盡可能的收集需要用到的值,然后在一個(gè)查詢中獲取所有結(jié)果。

      一個(gè)我們平時(shí)常常能見到查詢效率低下的地方 (例如:在循環(huán)中)是使用一個(gè)數(shù)組中的值 (比如說(shuō)很多的 ID )向表發(fā)起請(qǐng)求。檢索每一個(gè) ID 的所有的數(shù)據(jù),代碼將會(huì)迭代這個(gè)數(shù)組,每個(gè) ID 進(jìn)行一次SQL查詢請(qǐng)求,它看起來(lái)常常是這樣:

      $data = []; foreach ($ids as $id) {     $result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` = " . $id);     $data[] = $result->fetch_row(); }

      但是 只用一條 SQL 查詢語(yǔ)句就可以更高效的完成相同的工作,比如像下面這樣:

      $data = []; if (count($ids)) {     $result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` IN (" . implode(',', $ids));     while ($row = $result->fetch_row()) {         $data[] = $row;     } }

      因此在你的代碼直接或間接進(jìn)行查詢請(qǐng)求時(shí),一定要認(rèn)出這種查詢。盡可能的通過(guò)一次查詢得到想要的結(jié)果。然而,依然要小心謹(jǐn)慎,不然就可能會(huì)出現(xiàn)下面我們要講的另一個(gè)易犯的錯(cuò)誤…

      常見問(wèn)題 #5: 內(nèi)存使用欺騙與低效

      一次取多條記錄肯定是比一條條的取高效,但是當(dāng)我們使用 PHP 的 mysql 擴(kuò)展的時(shí)候,這也可能成為一個(gè)導(dǎo)致 libmysqlclient 出現(xiàn)『內(nèi)存不足』(out of memory)的條件。

      我們?cè)谝粋€(gè)測(cè)試盒里演示一下,該測(cè)試盒的環(huán)境是:有限的內(nèi)存(512MB RAM),MySQL,和 php-cli。

      我們將像下面這樣引導(dǎo)一個(gè)數(shù)據(jù)表:

      // 連接 mysql $connection = new mysqli('localhost', 'username', 'password', 'database');  // 創(chuàng)建 400 個(gè)字段 $query = 'CREATE TABLE `test`(`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT'; for ($col = 0; $col < 400; $col++) {     $query .= ", `col$col` CHAR(10) NOT NULL"; } $query .= ');'; $connection->query($query);  // 寫入 2 百萬(wàn)行數(shù)據(jù) for ($row = 0; $row < 2000000; $row++) {     $query = "INSERT INTO `test` VALUES ($row";     for ($col = 0; $col < 400; $col++) {         $query .= ', ' . mt_rand(1000000000, 9999999999);     }     $query .= ')';     $connection->query($query); }

      OK,現(xiàn)在讓我們一起來(lái)看一下內(nèi)存使用情況:

      // 連接 mysql $connection = new mysqli('localhost', 'username', 'password', 'database'); echo "Before: " . memory_get_peak_usage() . "n";  $res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 1'); echo "Limit 1: " . memory_get_peak_usage() . "n";  $res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 10000'); echo "Limit 10000: " . memory_get_peak_usage() . "n";

      輸出結(jié)果是:

      Before: 224704 Limit 1: 224704 Limit 10000: 224704

      Cool。 看來(lái)就內(nèi)存使用而言,內(nèi)部安全地管理了這個(gè)查詢的內(nèi)存。

      為了更加明確這一點(diǎn),我們把限制提高一倍,使其達(dá)到 100,000。 額~如果真這么干了,我們將會(huì)得到如下結(jié)果:

      PHP Warning:  mysqli::query(): (HY000/2013):               Lost connection to MySQL server during query in /root/test.php on line 11

      究竟發(fā)生了啥?

      這就涉及到 PHP 的 mysql 模塊的工作方式的問(wèn)題了。它其實(shí)只是個(gè) libmysqlclient 的代理,專門負(fù)責(zé)干臟活累活。每查出一部分?jǐn)?shù)據(jù)后,它就立即把數(shù)據(jù)放入內(nèi)存中。由于這塊內(nèi)存還沒(méi)被 PHP 管理,所以,當(dāng)我們?cè)诓樵兝镌黾酉拗频臄?shù)量的時(shí)候, memory_get_peak_usage() 不會(huì)顯示任何增加的資源使用情況 。我們被『內(nèi)存管理沒(méi)問(wèn)題』這種自滿的思想所欺騙了,所以才會(huì)導(dǎo)致上面的演示出現(xiàn)那種問(wèn)題。 老實(shí)說(shuō),我們的內(nèi)存管理確實(shí)是有缺陷的,并且我們也會(huì)遇到如上所示的問(wèn)題。

      如果使用 mysqlnd 模塊的話,你至少可以避免上面那種欺騙(盡管它自身并不會(huì)提升你的內(nèi)存利用率)。 mysqlnd 被編譯成原生的 PHP 擴(kuò)展,并且確實(shí) 會(huì) 使用 PHP 的內(nèi)存管理器。

      因此,如果使用 mysqlnd 而不是 mysql,我們將會(huì)得到更真實(shí)的內(nèi)存利用率的信息:

      Before: 232048 Limit 1: 324952 Limit 10000: 32572912

      順便一提,這比剛才更糟糕。根據(jù) PHP 的文檔所說(shuō),mysql 使用 mysqlnd 兩倍的內(nèi)存來(lái)存儲(chǔ)數(shù)據(jù), 所以,原來(lái)使用 mysql 那個(gè)腳本真正使用的內(nèi)存比這里顯示的

      贊(0)
      分享到: 更多 (0)
      網(wǎng)站地圖   滬ICP備18035694號(hào)-2    滬公網(wǎng)安備31011702889846號(hào)