12/08/2018, 13:37

Một số lỗi thường gặp của PHP developer

PHP giúp bạn dễ dàng xây dựng một hệ thống trên web, đó chính là một trong các lý do giải thích sự phổ biến của ngôn ngữ này. Tuy dễ sử dụng nhưng để sử dụng PHP hiệu quả thì lại là chuyện khác. PHP đã phát triển thành một ngôn ngữ khá phức tạp có thể làm mất hàng giờ debug của các developer. Sau ...

PHP giúp bạn dễ dàng xây dựng một hệ thống trên web, đó chính là một trong các lý do giải thích sự phổ biến của ngôn ngữ này. Tuy dễ sử dụng nhưng để sử dụng PHP hiệu quả thì lại là chuyện khác. PHP đã phát triển thành một ngôn ngữ khá phức tạp có thể làm mất hàng giờ debug của các developer. Sau đây là một số lỗi phổ biến với các lập trình viên.

Sự hiểu lầm về tác dụng của isset()

Dù tên là isset nhưng hàm isset() không chỉ trả về false khi item đó không tồn tại, nó cũng trả về false cho các giá trị null.

	$a = ['key2' => null, 'key3' => 1];
	isset($a['key1']); //return false
	isset($a['key2']); //return false
	isset($a['key3']); //return true

Hành động này sẽ gây ra nhiều vấn đề hơn khi được sử dụng không chính xác. Cùng xem ví dụ sau

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

Như đã nói ở trên isset($$ata['keyShouldBeSet']) sẽ trả về false nếu keyShouldBeSet được thiết lập nhưng với giá trị null. Logic trên vẫn còn thiếu sót.

Nếu bạn muốn kiểm tra một giá trị có thực sự được thiết lập(phân biệt giữa không được thiết lập và được thiết lập với giá tri null) thì array_key_exists() là một giải pháp mạnh mẽ và chắc chắn hơn.

	$a = ['key2' => null, 'key3' => 1];
	array_key_exists('key1', $a); //return false
	array_key_exists('key2', $a); //return false
	array_key_exists('key3', $a); //return true

Thực hiện các truy vấn trong vòng lặp

Không quá hiếm gặp những đoạn code như sau:

$models = [];

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

Về cơ bản thì không có gì sai lầm ở đây, nhưng nếu bạn để ý một chút đến logic ở đây bạn sẽ nhận ra rằng ngay vấn đề. Giả sư bạn chỉ muốn lấy kết quả của truy vấn sau:

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

Kết quả là với mỗi vòng lặp trên bạn sẽ thực hiện một truy vấn riêng biệt váo database. Nếu bạn đưa vào 1 mảng 1000 giá trị cho vòng lặp thì nó sẽ thực hiện 1000 truy vấn khác nhau và nếu nó được thực hiện trong multi thread thì khả năng hệ thống của bạn bị quá tải là rất lớn. Do đó, việc nhận ra các truy vấn sẽ được thực hiện như nào với code của bạn là rất quan trọng, và bất cứ khi nào có thể thì hãy thực hiện tập hợp các giá trị và thực hiện một truy vấn duy nhất để lấy về kết quả. Ví dụ với truy vấn sau:

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

ró ràng là nó không hiệu quả cho lắm và bạn có thể thực hiện với 1 câu query duy nhất như sau

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

Lạm dụng empty()

Có nhiều PHP Developer sử dụng empty() để kiểm tra boolean của gần như tất cả mọi thứ. Điều này đúng trong phần lớn các trường hợp, nhưng nó vẫn có thể dẫn đến sai lầm.

Xét mảng và thể hiện của ArrayObject, có thể bạn nghĩ rằng chúng giống nhau, nhưng không, không hoàn toàn là vậy, ta cùng xét ví dụ sau. Với PHP 5.0 trở lên

$array = [];
empty($array);        // outputs bool(true)
$array = new ArrayObject();
empty($array);        // outputs bool(false)

Tại sao kết quả lại khác nhau nếu chúng giống nhau. Nhưng với phiên bản PHP 5.0 trở về trước:

$array = [];
empty($array);        // outputs bool(false)
$array = new ArrayObject();
empty($array);        // outputs bool(false)

Điều này có vẻ khá tồi tệ nhưng đáng tiếc là nó lại khá phổ biến. Ví dụ đó chính là cách mà ZendDbTableGateway của Zend Framework 2 trả về dự liệu khi gọi current() của TableGateway::select() như khuyến cáo của document. Và nhiều developer đã trở thành nạn nhân của lỗi này với dữ liệu trên. Để tránh vấn đề này thì sử dụng count() để kiểm tra một mảng có phải là rỗng không là lựa trọn an toàn hơn:

$array = [];
var_dump(count($array));        // outputs int(0)
$array = new ArrayObject();
var_dump(count($array));        // outputs int(0)

Và khá tình cờ là kể từ khi PHP cast 0 thành false thì count() có thể sử dụng với if để check một mảng là rỗng. Hơn nữa count có độ phức tạp không đổi là O(1) với mọi trường hợp, vậy rõ ràng count là giải pháp đúng đắn.

Một trường hợp khác mà có thể dẫn đến sai lầm khi sử dụng empty() khi kết hợp với magic method __get().

Nghĩ rằng PHP hỗ trợ kiểu dữ liệu character

Hãy cùng xem xét đoạn code nhỏ sau:

for ($c = 'a'; $c <= 'z'; $c++) {
    echo $c . "
";
}

Bạn đoán rằng nó sẽ in ra gì? Chắc nhiều bạn sẽ nghĩ rằng các chữ cái từ 'a' đến 'z', nhưng sự thật lại ko phải vậy. Nó sẽ in ra các ký tự từ 'a' đến 'z' kèm theo đó là tất cả các ký từ 'aa' đến 'yz'. Tại sao vậy? Trong PHP không có kiểu dữ liệu char, chỉ có string thôi. Với tư tưởng đó thì increment của 'z' là 'aa'.

$c = 'z';
echo ++$c; //output 'aa'

Tuy nhiên vấn đề dễ gây nhầm lần hơn là 'aa' lại nhỏ hơn 'z' trong thứ tự từ điển.

var_export((boolean)('aa' < 'z')); //output bool(true)

Đó chính là lý do tại sao mà đoạn code trên lại in ra các kí tự từ 'a' đến 'z' kèm theo các ký tự từ 'aa' đến 'yz'.Nó chỉ dừng lại khi gặp 'za' giá trị đầu tiên lớn hơn 'z'. Vậy làm thế nào với trường hợp trên. Đây là 1 cách:

for ($i = ord('a'); $i <= ord('z'); $i++) {
    echo chr($i) . "
";
}

Lời kết

Việc dễ sử dụng có thể khiến bạn nhầm lẫn về cảm giác thoải mái khi dùng PHP, nó có thể khiến bạn mất khá nhiều thời gian để fix bug.

0