Cố gắng đưa ra một cái nhìn tổng quan về các cuộc thảo luận và câu trả lời khác nhau:
Không có câu trả lời duy nhất cho câu hỏi có thể thay thế tất cả các cách isset
có thể được sử dụng. Một số trường hợp sử dụng được giải quyết bởi các chức năng khác, trong khi các trường hợp khác không chịu sự giám sát, hoặc có giá trị đáng ngờ ngoài mã golf. Khác xa với việc "bị hỏng" hoặc "không nhất quán", các trường hợp sử dụng khác chứng minh tại sao isset
phản ứng của nó đối vớinull
là hành vi logic.
Các trường hợp sử dụng thực tế (có giải pháp)
1. Phím mảng
Mảng có thể được coi như tập hợp các biến, với unset
và isset
xử lý chúng như thể chúng là. Tuy nhiên, vì chúng có thể được lặp đi lặp lại, được tính, v.v., nên một giá trị bị thiếu không giống với giá trị lànull
.
Câu trả lời trong trường hợp này là sử dụng array_key_exists()
thay vìisset()
.
Vì đây là mảng để kiểm tra làm đối số hàm, PHP vẫn sẽ đưa ra "thông báo" nếu mảng đó không tồn tại. Trong một số trường hợp, có thể lập luận một cách hợp lệ rằng mỗi chiều nên được khởi tạo trước, vì vậy thông báo đang thực hiện công việc của nó. Đối với các trường hợp khác, một hàm "đệ quy" array_key_exists
, lần lượt kiểm tra từng chiều của mảng, sẽ tránh điều này, nhưng về cơ bản sẽ giống như @array_key_exists
. Nó cũng hơi tiếp xúc với việc xử lýnull
giá trị.
2. Thuộc tính đối tượng
Trong lý thuyết truyền thống về "Lập trình hướng đối tượng", đóng gói và đa hình là các thuộc tính chính của các đối tượng; trong việc thực hiện OOP dựa trên lớp như PHP, các thuộc tính đóng gói được khai báo như là một phần của định nghĩa lớp, và cho cấp độ truy cập ( public
, protected
hoặc private
).
Tuy nhiên, PHP cũng cho phép bạn tự động thêm các thuộc tính vào một đối tượng, giống như bạn khóa các mảng và một số người sử dụng các đối tượng không có lớp (về mặt kỹ thuật, các phiên bản được xây dựng stdClass
, không có phương thức hoặc chức năng riêng tư) tương tự cách để mảng kết hợp. Điều này dẫn đến các tình huống trong đó một chức năng có thể muốn biết nếu một thuộc tính cụ thể đã được thêm vào đối tượng được cung cấp cho nó.
Như với các khóa mảng, một giải pháp để kiểm tra các thuộc tính đối tượng được bao gồm trong ngôn ngữ, được gọi là, đủ hợp lý ,property_exists
.
Các trường hợp sử dụng không chính đáng, có thảo luận
3. register_globals
và ô nhiễm khác của không gian tên toàn cầu
Các register_globals
tính năng bổ sung các biến với phạm vi toàn cầu có tên được xác định bởi các khía cạnh của yêu cầu HTTP (GET và POST thông số, và cookie). Điều này có thể dẫn đến mã lỗi và không an toàn, đó là lý do tại sao nó đã bị tắt theo mặc định kể từ PHP 4.2, phát hành tháng 8 năm 2000 và bị xóa hoàn toàn trong PHP 5.4, phát hành tháng 3 năm 2012 . Tuy nhiên, có thể một số hệ thống vẫn đang chạy với tính năng này được kích hoạt hoặc mô phỏng. Cũng có thể "gây ô nhiễm" không gian tên toàn cầu theo những cách khác, bằng cách sử dụng global
từ khóa hoặc $GLOBALS
mảng.
Thứ nhất, register_globals
bản thân không có khả năng bất ngờ tạo ra một null
biến, vì GET, POST, và giá trị cookie sẽ luôn là chuỗi (với ''
vẫn trở về true
từ isset
), và các biến trong phiên giao dịch nên được hoàn toàn dưới sự kiểm soát của lập trình viên.
Thứ hai, ô nhiễm của một biến có giá trị null
chỉ là một vấn đề nếu điều này ghi đè lên một số khởi tạo trước đó. "Viết quá mức" một biến chưa được khởi tạo với null
sẽ chỉ có vấn đề nếu mã ở nơi khác phân biệt giữa hai trạng thái, do đó, khả năng này là một lập luận chống lại sự phân biệt như vậy.
4. get_defined_vars
vàcompact
Một vài hàm hiếm khi được sử dụng trong PHP, như get_defined_vars
, và compact
cho phép bạn xử lý các tên biến như thể chúng là các khóa trong một mảng. Đối với các biến toàn cục, mảng siêu toàn cầu$GLOBALS
cho phép truy cập tương tự và phổ biến hơn. Các phương thức truy cập này sẽ hành xử khác nhau nếu một biến không được xác định trong phạm vi có liên quan.
Khi bạn đã quyết định coi một tập hợp các biến là một mảng bằng một trong các cơ chế này, bạn có thể thực hiện tất cả các hoạt động tương tự trên nó như trên bất kỳ mảng thông thường nào. Do đó, xem 1.
Chức năng tồn tại chỉ để dự đoán các chức năng này sắp hoạt động như thế nào (ví dụ: "sẽ có một khóa 'foo' trong mảng được trả về bởi get_defined_vars
?") Là không cần thiết, vì bạn chỉ cần chạy chức năng và tìm ra không có hiệu ứng xấu.
4a. Biến biến ( $$foo
)
Mặc dù không hoàn toàn giống như các hàm biến một tập hợp biến thành một mảng kết hợp, nhưng hầu hết các trường hợp sử dụng "biến biến" ("gán cho một biến có tên dựa trên biến khác này") có thể và nên được thay đổi để sử dụng một mảng kết hợp thay thế .
Một tên biến, về cơ bản, là nhãn được lập trình viên đưa ra một giá trị; nếu bạn xác định nó vào thời gian chạy, nó không thực sự là nhãn mà là chìa khóa trong một số cửa hàng khóa-giá trị. Thực tế hơn, bằng cách không sử dụng một mảng, bạn đang mất khả năng đếm, lặp, v.v; nó cũng có thể trở thành không thể có một biến "bên ngoài" kho lưu trữ khóa-giá trị, vì nó có thể bị ghi đè bởi $$foo
.
Sau khi thay đổi để sử dụng một mảng kết hợp, mã sẽ có thể tuân theo giải pháp 1. Truy cập thuộc tính đối tượng gián tiếp (ví dụ $foo->$property_name
) có thể được xử lý bằng giải pháp 2.
5. isset
rất dễ gõarray_key_exists
Tôi không chắc điều này thực sự có liên quan, nhưng vâng, tên hàm của PHP có thể khá dài dòng và đôi khi không nhất quán. Rõ ràng, các phiên bản tiền sử của PHP đã sử dụng độ dài của tên hàm làm khóa băm, do đó Rasmus cố tình tạo ra các tên hàm như htmlspecialchars
vậy để chúng có số lượng ký tự khác thường ...
Tuy nhiên, ít nhất chúng ta không viết Java, eh? ;)
6. Các biến chưa được khởi tạo có một loại
Các trang hướng dẫn về cơ bản biến bao gồm tuyên bố này:
Các biến chưa được khởi tạo có giá trị mặc định của loại tùy thuộc vào ngữ cảnh mà chúng được sử dụng
Tôi không chắc liệu có một số khái niệm trong Zend Engine về "loại chưa được khởi tạo nhưng đã biết" hay liệu điều này có đang đọc quá nhiều vào tuyên bố hay không.
Điều rõ ràng là nó không tạo ra sự khác biệt thực tế đối với hành vi của họ, vì các hành vi được mô tả trên trang đó đối với các biến chưa được khởi tạo giống hệt với hành vi của một biến có giá trị là null
. Để chọn một ví dụ, cả hai $a
và $b
trong mã này sẽ kết thúc dưới dạng số nguyên 42
:
unset($a);
$a += 42;
$b = null;
$b += 42;
(Đầu tiên sẽ đưa ra một thông báo về một biến không được khai báo, trong nỗ lực làm cho bạn viết mã tốt hơn, nhưng nó sẽ không tạo ra bất kỳ sự khác biệt nào đối với cách mã thực sự chạy.)
99. Phát hiện nếu một chức năng đã chạy
(Giữ cái này cuối cùng, vì nó dài hơn những cái khác. Có lẽ tôi sẽ chỉnh sửa nó sau ...)
Hãy xem xét các mã sau đây:
$test_value = 'hello';
foreach ( $list_of_things as $thing ) {
if ( some_test($thing, $test_value) ) {
$result = some_function($thing);
}
}
if ( isset($result) ) {
echo 'The test passed at least once!';
}
Nếu some_function
có thể trở lại null
, có khả năng echo
sẽ không đạt được mặc dù đã some_test
trả lại true
. Ý định của lập trình viên là phát hiện khi nào $result
chưa bao giờ được thiết lập, nhưng PHP không cho phép họ làm như vậy.
Tuy nhiên, có một số vấn đề khác với cách tiếp cận này, sẽ trở nên rõ ràng nếu bạn thêm một vòng lặp bên ngoài:
foreach ( $list_of_tests as $test_value ) {
// something's missing here...
foreach ( $list_of_things as $thing ) {
if ( some_test($thing, $test_value) ) {
$result = some_function($thing);
}
}
if ( isset($result) ) {
echo 'The test passed at least once!';
}
}
Bởi vì $result
không bao giờ được khởi tạo một cách rõ ràng, nó sẽ đảm nhận một giá trị khi lần thử nghiệm đầu tiên trôi qua, khiến cho không thể biết liệu các thử nghiệm tiếp theo có vượt qua hay không. Đây thực sự là một lỗi cực kỳ phổ biến khi các biến không được khởi tạo đúng cách.
Để khắc phục điều này, chúng tôi cần phải làm một cái gì đó trên dòng mà tôi đã nhận xét rằng thiếu một cái gì đó. Giải pháp rõ ràng nhất là đặt thành $result
"giá trị đầu cuối" some_function
không bao giờ có thể quay lại; nếu điều này là null
, phần còn lại của mã sẽ hoạt động tốt. Nếu không có ứng cử viên tự nhiên cho giá trị đầu cuối vì some_function
có loại trả về cực kỳ khó đoán (có thể là dấu hiệu xấu trong chính nó), thì $found
có thể sử dụng giá trị boolean bổ sung, ví dụ:
Suy nghĩ thử nghiệm một: very_null
hằng số
Về mặt lý thuyết, PHP có thể cung cấp một hằng số đặc biệt - cũng như null
- để sử dụng làm giá trị đầu cuối ở đây; có lẽ, việc trả lại điều này từ một hàm là bất hợp pháp, hoặc nó sẽ bị ép buộc null
, và điều tương tự có thể sẽ được áp dụng để chuyển nó thành một đối số hàm. Điều đó sẽ làm cho trường hợp rất cụ thể này trở nên đơn giản hơn một chút, nhưng ngay khi bạn quyết định tính lại mã - ví dụ, để đặt vòng lặp bên trong vào một hàm riêng biệt - nó sẽ trở nên vô dụng. Nếu hằng số có thể được truyền giữa các hàm, bạn không thể đảm bảo rằng nó some_function
sẽ không trả về nó, vì vậy nó sẽ không còn hữu ích như một giá trị đầu cuối phổ quát.
Đối số để phát hiện các biến chưa được khởi tạo trong trường hợp này rút ra đối số cho hằng số đặc biệt đó: nếu bạn thay thế nhận xét unset($result)
và xử lý khác với đó $result = null
, bạn đang đưa ra một "giá trị" cho $result
điều đó không thể được thông qua và chỉ có thể được chuyển được phát hiện bởi các chức năng tích hợp cụ thể.
Thử nghiệm suy nghĩ hai: bộ đếm chuyển nhượng
Một cách nghĩ khác về những gì cuối cùng if
đang hỏi là "có gì được giao $result
không?" Thay vì coi nó là một giá trị đặc biệt $result
, bạn có thể nghĩ đây là "siêu dữ liệu" về biến, giống như "biến đổi" của Perl. Vì vậy, thay vì isset
bạn có thể gọi nó has_been_assigned_to
, và hơn là unset
, reset_assignment_state
.
Nhưng nếu vậy, tại sao dừng lại ở một boolean? Điều gì sẽ xảy ra nếu bạn muốn biết thử nghiệm đã vượt qua bao nhiêu lần ; bạn chỉ có thể mở rộng siêu dữ liệu của mình thành một số nguyên và có get_assignment_count
và reset_assignment_count
...
Rõ ràng, việc thêm một tính năng như vậy sẽ có sự đánh đổi về độ phức tạp và hiệu suất của ngôn ngữ, vì vậy nó sẽ cần được cân nhắc cẩn thận với tính hữu dụng dự kiến của nó. Như với một very_null
hằng số, nó sẽ chỉ hữu ích trong các trường hợp rất hẹp và có khả năng chống lại sự bao thanh toán tương tự.
Câu hỏi hy vọng rõ ràng là tại sao công cụ thời gian chạy PHP nên giả định trước rằng bạn muốn theo dõi những điều đó, thay vì để bạn làm điều đó một cách rõ ràng, sử dụng mã bình thường.