Hướng dẫn
Quảng cáo

6 SỰ THẬT VỀ VÒNG LẶP FOREACH TRONG PHP

Cấu trúc vòng lặp Foreach được sử dụng nhiều trong khi lập trình PHP. Bài viết này sẽ hướng dẫn các bạn cách tránh lỗi khi dùng vòng lặp foreach

Hầu hết mọi ngôn ngữ lập trình đều hỗ trợ vòng lặp foreach và PHP cũng không ngoại lệ. Tôi tin rằng chắc chắn là bạn cũng đã sử dụng nó rất nhiều lần.

Không có gì đặc biệt về vòng lặp foreach trong PHP nhưng tôi nghĩ có một số điều bạn cần biết . Cấu trúc điều khiển vòng lặp được sử dụng mọi lúc, vì vậy điều quan trọng là không mắc lỗi khi sử dụng chúng.

Trong bài viết hôm nay tôi sẽ chỉ cho bạn  6 sự thật mà mọi nhà phát triển PHP nên biết về vòng lặp foreach.

1. ÉP KIỂU INT CỦA KEY

PHP có một số tính năng thân thiện với người dùng giúp người mới bắt đầu và các nhà phát triển ít kinh nghiệm viết mã dễ dàng hơn. Một trong số đó là sắp xếp kiểu : trình thông dịch PHP tự động đặt kiểu của biến tùy thuộc vào ngữ cảnh và toán tử nào được sử dụng.

Điều này không có nghĩa là PHP không có loại. Trên thực tế, mỗi biến đều có kiểu riêng  nhưng nó có thể thay đổi theo thời gian.

$a = 1 tạo $a thành số nguyên và  $a = '1' tạo $a thành chuỗi. Trong cả hai trường hợp, ngay cả khi loại khác nhau, một câu lệnh như  $a = $a + 1 sẽ làm cho  $a trở thành một số nguyên.

Mặc dù việc tung hứng kiểu có thể thuận tiện nhưng điều quan trọng là phải luôn xem xét loại biến mà chúng ta đang xử lý, đặc biệt khi sử dụng các toán tử so sánh chặt chẽ (như “ === “). Các toán tử so sánh chặt chẽ rất hữu ích vì chúng có thể phân biệt giữa các giá trị như 0 , FALSE hoặc NULL , trong khi các toán tử so sánh lỏng lẻo (như “ == “) coi chúng tương đương.

Hãy đi vào vấn đề. Bên trong vòng lặp foreach, bạn có thể truy cập mọi cặp khóa/giá trị (key/value) của mảng bạn đang lặp và đôi khi bạn có thể cần so sánh khóa với một số giá trị khác. Vấn đề là thế này: nếu và chỉ nếu khóa là một chữ số thì nó sẽ tự động được chuyển đổi thành số nguyên khi bắt đầu vòng lặp .

Do đó, việc so sánh nghiêm ngặt nó với một chuỗi sẽ luôn trả về FALSE .

Ví dụ sau đây cho thấy rõ vấn đề: 

Ví dụ

/* We define a two-elements array. Note that the second key is a numeric literal string. */
$array = array(
   'first' => 'first element',
   '2' => 'second element'
);
/* Here is what happens */
foreach ($array as $key => $value)
{
   echo 'Key is: ' . $key . '<br>';
   echo 'Key type is: ' . gettype($key) . '<br>';
   
   /* Let's try a comparison */
   if ($key === '2')
   {
      echo 'Key is "2"!<br>';
   }
   else
   {
      echo 'Key is NOT "2"!<br>';
   }
}

Đầu ra là:

Key is: first
Key type is: string
Key is NOT “2”!
Key is: 2
Key type is: integer
Key is NOT “2”!

Như bạn có thể thấy ở kết quả đầu ra, mặc dù khóa mảng thứ hai thực sự là một chuỗi ('2'), nhưng nó tự động được chuyển thành một số nguyên bên trong vòng lặp vì nó là một chữ số. Nếu chúng ta cố gắng so sánh chặt chẽ nó với '2', biểu thức sẽ trả về false .

Một giải pháp cho vấn đề này là giải thích việc chuyển khóa thành chuỗi ở đầu mỗi vòng lặp (trên dòng 13):

Ví dụ

/* We define a two-elements array. Note that the second key is a numeric literal string. */
$array = array(
   'first' => 'first element',
   '2' => 'second element'
);
/* Here is what happens */
foreach ($array as $key => $value)
{
   $key = strval($key);
   echo 'Key is: ' . $key . '<br>';
   echo 'Key type is: ' . gettype($key) . '<br>';
   
   /* Let's try a comparison */
   if ($key === '2')
   {
      echo 'Key is "2"!<br>';
   }
   else
   {
      echo 'Key is NOT "2"!<br>';
   }
}


Lần này đầu ra là:

Key is: first
Key type is: string
Key is NOT “2”!
Key is: 2
Key type is: string
Key is “2”!

Bây giờ, so sánh chặt chẽ giữa khóa thứ hai và '2' trả về true , như chúng tôi mong đợi.

2. SỬA ĐỔI MỘT MẢNG TỪ BÊN TRONG VÒNG LẶP

Nếu bạn đang sử dụng foreach để lặp qua một mảng và bạn muốn sửa đổi chính mảng đó thì có hai cách để thực hiện điều đó.

Cách đầu tiên là trỏ đến phần tử mảng bạn muốn chỉnh sửa bằng cú pháp $array[$key] (vì vậy bạn phải sử dụng cú pháp foreach $key => $value trong phần đầu foreach).

Cách khác là xác định $value làm tham chiếu và chỉnh sửa trực tiếp. 

Hai ví dụ sau đây cho thấy cách thực hiện:

 

Ví dụ

$array = array(1, 2);
foreach ($array as $key => $value)
{
   $array[$key] += 1;
}
print_r($array);

Ví dụ

<?php
$array = array(1, 2);
foreach ($array as $key => &$value)
{
   $value += 1;
}
print_r($array);


Trong cả hai trường hợp, đầu ra sẽ là:

Array
(
[0] => 2
[1] => 3
)

3. GHI ĐÈ BIẾN

Điều quan trọng cần lưu ý là vòng lặp foreach không có phạm vi riêng . Điều này có nghĩa là các biến được xác định bên ngoài vòng lặp cũng có sẵn bên trong nó và bất kỳ biến nào được khai báo bên trong vòng lặp sẽ tiếp tục có thể truy cập được ngay cả sau khi vòng lặp kết thúc.

Điều này cũng áp dụng cho các biến $key => $value được sử dụng trong phần đầu foreach.

Ví dụ này cho thấy những gì có thể xảy ra: 

Ví dụ

$array = array(1, 2);
$value = 'my value';
foreach ($array as $key => $value)
{
   // do something
}
echo $value; /* Output is "2" */

 

Sau vòng lặp, $value sẽ không chứa 'my value' nữa mà thay vào đó là giá trị 2 , vì nó đã bị ghi đè bởi phép gán foreach.

Trong khi tôi đang tìm kiếm thêm thông tin chi tiết về vấn đề này, tôi đã tìm thấy bài đăng thú vị này  nêu bật một mối nguy hiểm tiềm ẩn khác.

Trước tiên hãy xem một ví dụ:  

Ví dụ

$array = array(1, 2, 3);
foreach ($array as $key => &$value)
{
   // do something
}
/* Everything ok here */
print_r($array);
foreach ($array as $key => $value)
{
   // do something
}
/* Something wrong! */
print_r($array);


Đầu ra từ hai print_r() là:

Array
(
[0] => 1
[1] => 2
[2] => 3
)
Array
(
[0] => 1
[1] => 2
[2] => 2
)

Sau vòng lặp foreach thứ hai, phần tử cuối cùng trong mảng đã thay đổi. Chuyện gì đã xảy ra thế?

Trong phần foreach đầu tiên, chúng tôi sử dụng cú pháp &$value , vì vậy $value được đặt làm tham chiếu cho từng phần tử của mảng. Do đó , trong vòng lặp cuối cùng, $value tương đương với $array[2] .

Sau khi foreach kết thúc, $value vẫn có thể truy cập được (vì foreach không có phạm vi riêng).

Khi foreach thứ hai bắt đầu, $value vẫn bị ràng buộc với phần tử cuối cùng của mảng, do đó, trong mỗi vòng lặp, cả $value và $array[2] đều bị ghi đè .

Đây là những gì xảy ra chi tiết hơn:

  • Sau lần giới thiệu đầu tiên, $value trỏ tới $array[2] .
  • Foreach thứ hai, lần lặp đầu tiên: $array[2] được đặt với phần tử mảng đầu tiên (vì vậy, $array[2] = 1 ).
  • Foreach thứ hai, lần lặp thứ hai: $array[2] được đặt với phần tử mảng thứ hai (vì vậy, $array[2] = 2 ).
  • Foreach thứ hai, lần lặp thứ ba: $array[2] được đặt với phần tử mảng thứ ba (tức là chính nó, vì vậy $array[2] = 2 ).

Điều đó giải thích kết quả.

Để tránh vấn đề này, bạn nên bỏ đặt bất kỳ biến nào được sử dụng trong vòng lặp foreach, đặc biệt là khi sử dụng tham chiếu.

4. CÓ CẦN RESET() KHÔNG?

Trước PHP 7, foreach dựa vào con trỏ mảng bên trong để lặp, vì vậy bạn nên sử dụng reset() sau đó (và trước PHP 5, trước khi bắt đầu vòng lặp).

Điều này không còn xảy ra với PHP 7 nữa, vì giờ đây foreach sử dụng con trỏ bên trong của chính nó. Điều này giúp chúng ta tiết kiệm được một vài dòng mã.

Ví dụ sau đây cho thấy rằng sau vòng lặp foreach, con trỏ bên trong của mảng vẫn trỏ đến phần tử đầu tiên:

Ví dụ

$array = array(1, 2, 3);
foreach ($array as $item)
{
   echo 'Item: ' . $item . '<br>';
}
/* Current item is still 1 */
echo 'Current item: ' . current($array); /* Outputs 1 */

5. HIỆU SUẤT FOREACH VS FOR VS WHILE

Khi cần lặp qua một mảng, bạn cũng có thể sử dụng vòng lặp while hoặc vòng lặp for thay vì sử dụng foreach.

Cách nào là tốt nhất về hiệu suất?

Tôi đã chạy thử nghiệm với một mảng gồm 1 triệu mặt hàng. Khi sử dụng các phím số nguyên, hóa ra cả ba cấu trúc điều khiển đều chạy trong khoảng thời gian như nhau (0,02 giây trên máy thử nghiệm của tôi).

Tuy nhiên, nếu chúng ta đang xử lý các mảng kết hợp (tức là mảng có khóa chuỗi), vòng lặp foreach nhanh hơn gấp đôi so với hai cấu trúc điều khiển còn lại. Điều này có thể là do cấu trúc while và for cần sử dụng hàm phụ trợ để truy xuất các phần tử của mảng, như current() và next() .

Foreach thường là cách dễ nhất để lặp qua các mảng và hóa ra nó cũng là cách nhanh nhất, vì vậy đây sẽ là lựa chọn ưu tiên của bạn.

Tuy nhiên, điều đáng nói là trong các tình huống thực tế, sự khác biệt về hiệu suất sẽ gần như không đáng kể, trừ khi bạn cần xử lý các mảng thực sự lớn (và trong trường hợp đó, có lẽ bạn nên sửa ứng dụng của mình trước…).

Nếu bạn muốn xem mã kiểm tra, chỉ cần nhấp vào đây để hiển thị nó.

Kiểm tra cấu trúc điều khiển

Ví dụ

/* Set this to avoid memory errors */
ini_set('memory_limit','1024M');

$array = array();
/* Use this for creating a numeric array */
for ($i = 0; $i < 1000000; $i++)
{
   $array[$i] = $i;
}
/* Or use this for creating an associative array */
for ($i = 0; $i < 1000000; $i++)
{
   $array[$i] = strval($i) . 'string';
}
$count = count($array);
$start = microtime(TRUE);
/* Foreach loop, ok for both array types */
foreach ($array as $item)
{
   $var = $item;
}
/* For loop only for numeric arrays */
for ($i = 0; $i < $count; $i++)
{
   $var = $array[$i];
}
/* While loop only for numeric arrays */
$i = 0;
while ($i < $count)
{
   $var = $array[$i];
   $i++;
}
/* For loop for associative arrays */
for ($item = current($array); $item !== FALSE; $item = next($array))
{
   $var = $item;
}
/* While loop for associative arrays */
$item = current($array);
while ($item !== FALSE)
{
   $var = $item;
   $item = next($array);
}
$end = microtime(TRUE);
echo ($end - $start);

6. HIỆU SUẤT KEY => VALUE

Các vòng lặp Foreach của PHP hỗ trợ cả cú pháp $array as $value và cú pháp $array as $key => $value .

Cú pháp đầu tiên cũng nhanh nhất xét về hiệu suất: trong thử nghiệm của tôi, việc sử dụng cú pháp thứ hai dẫn đến thời gian thực thi dài hơn 50% . Nếu bạn không cần chìa khóa, có lẽ bạn nên sử dụng cú pháp đầu tiên.

Mặc dù vậy, các con số quá nhỏ nên thật không may là bạn sẽ nhận thấy bất kỳ sự khác biệt nào trong các tình huống thực tế, vì vậy đừng quá lo lắng về điều đó (trên máy thử nghiệm của tôi, hai bài kiểm tra chạy trong khoảng 0,2 và 0,3 giây, trong khoảng thời gian khoảng 0,2 và 0,3 giây). mảng 10 triệu phần tử).

 

Trên đây là tất cả 6 điều bạn cần biết về vòng lặp foreach trong PHP. Nếu bạn muốn một số ví dụ khác hoặc có bất kỳ câu hỏi nào, chỉ cần để lại nhận xét bên dưới.

 Như mọi khi, tôi thực sự cảm ơn bạn đã đọc bài đăng này và nếu bạn thích nó, vui lòng dành chút thời gian để chia sẻ nó! 

Bài viết này đã giúp ích cho bạn?

Bài viết mới

Advertisements