Gõ các biến đúc trong PHP, lý do thực tế để làm điều này là gì?


45

PHP, như hầu hết chúng ta đều biết, có kiểu gõ yếu . Đối với những người không, PHP.net nói:

PHP không yêu cầu (hoặc hỗ trợ) định nghĩa kiểu rõ ràng trong khai báo biến; một loại biến được xác định bởi bối cảnh sử dụng biến đó.

Yêu hay ghét nó, PHP lại đưa ra các biến khi đang di chuyển. Vì vậy, đoạn mã sau là hợp lệ:

$var = "10";
$value = 10 + $var;
var_dump($value); // int(20)

PHP cũng cho phép bạn truyền một cách rõ ràng một biến, như vậy:

$var = "10";
$value = 10 + $var;
$value = (string)$value;
var_dump($value); // string(2) "20"

Điều đó thật tuyệt ... nhưng, đối với cuộc sống của tôi, tôi không thể hình dung ra một lý do thực tế để làm điều này.

Tôi không gặp vấn đề với việc gõ mạnh vào các ngôn ngữ hỗ trợ nó, như Java. Điều đó tốt, và tôi hoàn toàn hiểu nó. Ngoài ra, tôi biết - và hiểu đầy đủ về tính hữu ích của - gợi ý loại trong các tham số chức năng.

Vấn đề tôi có với kiểu đúc được giải thích bằng trích dẫn ở trên. Nếu PHP có thể hoán đổi các loại theo ý muốn , thì nó có thể làm như vậy ngay cả sau khi bạn buộc ép kiểu; và nó có thể thực hiện nhanh chóng khi bạn cần một loại nhất định trong một hoạt động. Điều đó làm cho các giá trị sau:

$var = "10";
$value = (int)$var;
$value = $value . ' TaDa!';
var_dump($value); // string(8) "10 TaDa!"

Vậy quan điểm là gì?


Lấy ví dụ lý thuyết này về một thế giới nơi việc truyền kiểu do người dùng định nghĩa có ý nghĩa trong PHP :

  1. Bạn buộc biến cast $fooint(int)$foo.
  2. Bạn cố gắng lưu trữ một giá trị chuỗi trong biến $foo.
  3. PHP ném một ngoại lệ !! ← Điều đó sẽ có ý nghĩa. Đột nhiên, lý do cho người dùng xác định loại đúc tồn tại!

Việc PHP sẽ chuyển đổi mọi thứ xung quanh khi cần thiết khiến cho việc xác định kiểu người dùng xác định kiểu mơ hồ. Ví dụ, hai mẫu mã sau đây là tương đương:

// example 1
$foo = 0;
$foo = (string)$foo;
$foo = '# of Reasons for the programmer to type cast $foo as a string: ' . $foo;

// example 2
$foo = 0;
$foo = (int)$foo;
$foo = '# of Reasons for the programmer to type cast $foo as a string: ' . $foo;

Một năm sau khi ban đầu đặt câu hỏi này, hãy đoán xem ai đã tìm thấy chính mình bằng cách sử dụng typecasting trong một môi trường thực tế? Bạn thật sự.

Yêu cầu là hiển thị giá trị tiền trên một trang web cho menu nhà hàng. Thiết kế của trang web yêu cầu các số 0 ở cuối được cắt bớt, để màn hình hiển thị trông giống như sau:

Menu Item 1 .............. $ 4
Menu Item 2 .............. $ 7.5
Menu Item 3 .............. $ 3

Cách tốt nhất tôi tìm thấy để làm lãng phí đó để biến biến thành một dấu phẩy:

$price = '7.50'; // a string from the database layer.
echo 'Menu Item 2 .............. $ ' . (float)$price;

PHP cắt các số 0 ở cuối của float, và sau đó lấy lại số float như một chuỗi để nối.


Điều này -> $ value = $ value. 'TaDa!'; Sẽ chuyển $ value trở lại chuỗi trước khi thực hiện gán cho giá trị cuối cùng của giá trị $. Không thực sự ngạc nhiên rằng nếu bạn ép một loại diễn viên, bạn sẽ có được một loại diễn viên. Không chắc vấn đề là gì khi hỏi điểm của nó là gì?
Chris

"# 3. PHP ném một ngoại lệ !! <--- Điều đó sẽ có ý nghĩa." Trên thực tế điều đó sẽ không có ý nghĩa gì cả. Đó thậm chí không phải là vấn đề trong Java, JavaScript hoặc bất kỳ ngôn ngữ cú pháp C nào khác mà tôi biết. Ai trong tâm trí của họ sẽ xem đó là hành vi mong muốn? Bạn có muốn có (string)phôi ở khắp mọi nơi ?
Nicole

@Renesis: bạn hiểu lầm tôi. Điều tôi muốn nói là một ngoại lệ sẽ chỉ được ném nếu người dùng đã gõ một biến. Hành vi bình thường (trong đó PHP thực hiện việc phân vai cho bạn) tất nhiên sẽ không đưa ra một ngoại lệ. Tôi đang cố gắng để nói rằng người dùng định nghĩa loại đúc là tranh luận , nhưng nếu một ngoại lệ đã được ném nó đột nhiên sẽ có ý nghĩa.
Stephen

Nếu bạn đang $intval.'bar'ném một ngoại lệ, tôi vẫn không đồng ý. Điều đó không ném một ngoại lệ trong bất kỳ ngôn ngữ. (Tất cả các ngôn ngữ tôi biết thực hiện là diễn viên tự động hoặc a .toString()). Nếu bạn đang nói $intval = $stringvalném một ngoại lệ, thì bạn đang nói về một ngôn ngữ được gõ mạnh. Tôi không có ý nói thô lỗ, vì vậy, xin lỗi nếu tôi đã làm. Tôi chỉ nghĩ rằng nó đi ngược lại với những gì mọi nhà phát triển đã quen và ít tiện lợi hơn nhiều.
Nicole

@Stephen - Tôi đã đăng một câu trả lời sau một số điều tra. Kết quả thực sự thú vị - Tôi nghĩ rằng 2 trong số các trường hợp chắc chắn sẽ hiển thị mục đích để truyền, nhưng PHP thậm chí còn kỳ lạ hơn tôi nghĩ.
Nicole

Câu trả lời:


32

Trong một ngôn ngữ được gõ yếu, việc tạo kiểu tồn tại để loại bỏ sự mơ hồ trong các hoạt động được gõ, khi không thì trình biên dịch / trình thông dịch sẽ sử dụng thứ tự hoặc các quy tắc khác để đưa ra giả định sử dụng thao tác nào.

Thông thường tôi sẽ nói PHP tuân theo mô hình này, nhưng trong các trường hợp tôi đã kiểm tra, PHP đã hành xử theo trực giác trong từng trường hợp.

Dưới đây là những trường hợp, sử dụng JavaScript làm ngôn ngữ so sánh.

Chuỗi liên kết

Rõ ràng đây không phải là một vấn đề trong PHP bởi vì có các toán tử nối chuỗi ( .) và phép cộng ( +) riêng biệt .

JavaScript
var a = 5;
var b = "10"
var incorrect = a + b; // "510"
var correct = a + Number(b); // 15

So sánh chuỗi

Thông thường trong các hệ thống máy tính "5" lớn hơn "10" vì nó không hiểu nó là một số. Không phải như vậy trong PHP, mà ngay cả khi cả hai đều là chuỗi, nhận ra chúng là số và loại bỏ sự cần thiết phải truyền):

JavaScript
console.log("5" > "10" ? "true" : "false"); // true
PHP
echo "5" > "10" ? "true" : "false";  // false!

Chức năng gõ chữ ký

PHP thực hiện kiểm tra kiểu xương trần trên chữ ký hàm, nhưng thật không may, nó rất thiếu sót nên có lẽ hiếm khi sử dụng được.

Tôi nghĩ rằng tôi có thể đang làm gì đó sai, nhưng một nhận xét về các tài liệu xác nhận rằng các kiểu dựng sẵn không phải là mảng có thể được sử dụng trong chữ ký hàm PHP - mặc dù thông báo lỗi là sai.

PHP
function testprint(string $a) {
    echo $a;
}

$test = 5;
testprint((string)5); // "Catchable fatal error: Argument 1 passed to testprint()
                      //  must be an instance of string, string given" WTF?

Và không giống như bất kỳ ngôn ngữ nào khác mà tôi biết, ngay cả khi bạn sử dụng một loại ngôn ngữ mà nó hiểu, null không còn có thể được chuyển cho đối số đó ( must be an instance of array, null given). Thật ngu ngốc.

Giải thích Boolean

[ Chỉnh sửa ]: Cái này là mới. Tôi đã nghĩ đến một trường hợp khác và một lần nữa logic được đảo ngược từ JavaScript.

JavaScript
console.log("0" ? "true" : "false"); // True, as expected. Non-empty string.
PHP
echo "0" ? "true" : "false"; // False! This one probably causes a lot of bugs.

Vì vậy, kết luận, trường hợp hữu ích duy nhất tôi có thể nghĩ đến là ... (trống)

Loại cắt ngắn

Nói cách khác, khi bạn có một giá trị của một loại (nói chuỗi) và bạn muốn diễn giải nó thành một loại (int) khác và bạn muốn buộc nó trở thành một trong những giá trị hợp lệ trong loại đó:

$val = "test";
$val2 = "10";
$intval = (int)$val; // 0
$intval2 = (int)$val2; // 10
$boolval = (bool)$intval // false
$boolval2 = (bool)$intval2 // true
$props = (array)$myobject // associative array of $myobject's properties

Tôi không thể thấy những gì u ám (đến một loại bao gồm nhiều giá trị hơn) sẽ thực sự mang lại cho bạn.

Vì vậy, trong khi tôi không đồng ý với cách sử dụng gõ của bạn (về cơ bản bạn đang đề xuất gõ tĩnh , nhưng với sự mơ hồ rằng chỉ khi nó bị ép thành một loại thì nó sẽ gây ra lỗi - điều này sẽ gây nhầm lẫn), tôi nghĩ đó là một điều tốt câu hỏi, bởi vì rõ ràng việc đúc có rất ít mục đích trong PHP.


Được rồi, thế còn một E_NOTICEthì sao? :)
Stephen

@Stephen E_NOTICEcó thể ổn, nhưng với tôi trạng thái mơ hồ có liên quan - làm thế nào bạn biết bằng cách xem một bit mã nếu biến ở trạng thái đó (đã được đặt ở một nơi khác)? Ngoài ra, tôi tìm thấy một điều kiện khác và thêm nó vào câu trả lời của tôi.
Nicole

1
Đối với đánh giá Boolean, các tài liệu PHP nêu rõ những gì được coi là sai khi đánh giá thành boolean và cả chuỗi rỗng và chuỗi "0" được coi là sai. Vì vậy, ngay cả khi điều này cảm thấy kỳ quái, đó là hành vi bình thường và mong đợi.
Jacek Prucia

để thêm bit vào nhầm lẫn: echo "010" == 010echo "0x10" == 0x10;-)
vartec

1
Lưu ý rằng kể từ PHP 7 , ghi chú của câu trả lời này về gợi ý kiểu vô hướng là không chính xác.
John V.

15

Bạn đang trộn các khái niệm loại yếu / mạnh và động / tĩnh.

PHP yếu và năng động, nhưng vấn đề của bạn là về khái niệm kiểu động. Điều đó có nghĩa là, các biến không có loại, giá trị nào.

'Đúc kiểu' là một biểu thức tạo ra giá trị mới của một loại khác của bản gốc; nó không làm gì với biến (nếu có liên quan).

Một tình huống trong đó tôi thường xuyên nhập các giá trị truyền là trên các tham số SQL số. Bạn phải vệ sinh / thoát khỏi mọi giá trị đầu vào mà bạn chèn vào các câu lệnh SQL hoặc (tốt hơn nhiều) sử dụng các truy vấn được tham số hóa. Nhưng, nếu bạn muốn một số giá trị PHẢI là một số nguyên, việc bỏ nó sẽ dễ dàng hơn nhiều.

Xem xét:

function get_by_id ($id) {
   $id = (int)$id;
   $q = "SELECT * FROM table WHERE id=$id LIMIT 1";
   ........
}

nếu tôi rời khỏi dòng đầu tiên, $idsẽ là một vectơ dễ dàng cho SQL tiêm. Các diễn viên đảm bảo rằng đó là một số nguyên vô hại; mọi nỗ lực để chèn một số SQL chỉ đơn giản là dẫn đến một truy vấn choid=0


Tôi sẽ chấp nhận điều đó. Bây giờ, như tính hữu dụng của Type Casting?
Stephen

Thật buồn cười khi bạn đưa ra SQL tiêm. Tôi đã tranh cãi về SO với ai đó sử dụng kỹ thuật này để vệ sinh đầu vào của người dùng. Nhưng vấn đề gì phương pháp này không giải quyết mysql_real_escape_string($id);được?
Stephen

tất nhiên là ngắn hơn :-), đối với các chuỗi tôi sử dụng các truy vấn được tham số hóa hoặc (nếu sử dụng phần mở rộng mysql cũ) thoát khỏi nó.
Javier

2
mysql_real_escape_string()có một lỗ hổng không làm gì với các chuỗi như '0x01ABCDEF' (nghĩa là biểu diễn thập lục phân của một số nguyên). Trong một số mã hóa đa bào (không phải là Unicode may mắn), một chuỗi như thế này có thể được sử dụng để phá vỡ truy vấn (bởi vì nó được MySQL đánh giá thành thứ gì đó có chứa trích dẫn). Đó là lý do tại sao không phải mysql_real_escape_string()cũng không phải is_int()là lựa chọn tốt nhất để xử lý các giá trị nguyên. Đánh máy là.
Mchl

Một liên kết với một số chi tiết khác: ilia.ws/archives/ Sự
Mchl

4

Một cách sử dụng để truyền kiểu trong PHP mà tôi đã tìm thấy:

Tôi đang phát triển một ứng dụng Android thực hiện các yêu cầu http đến các tập lệnh PHP trên máy chủ để truy xuất dữ liệu từ cơ sở dữ liệu. Kịch bản lệnh lưu trữ dữ liệu dưới dạng đối tượng PHP (hoặc mảng kết hợp) và được trả về dưới dạng đối tượng JSON cho ứng dụng. Nếu không có kiểu đúc tôi sẽ nhận được một cái gì đó như thế này:

{ "user" : { "id" : "1", "name" : "Bob" } }

Nhưng, bằng cách sử dụng kiểu đúc PHP (int)trên id của người dùng khi lưu trữ đối tượng PHP, tôi sẽ nhận được điều này trở lại ứng dụng:

{ "user" : { "id" : 1, "name" : "Bob" } }

Sau đó, khi đối tượng JSON được phân tích cú pháp trong ứng dụng, nó sẽ giúp tôi không phải phân tích cú pháp id thành Integer!

Xem, rất hữu ích.


Tôi đã không xem xét định dạng dữ liệu cho các hệ thống gõ mạnh bên ngoài để tiêu thụ. +1
Stephen

Điều này đặc biệt đúng khi nói JSON với các hệ thống bên ngoài như Elaticsearch. Giá trị json_encode () - ed "5" sẽ cho kết quả rất khác so với giá trị 5.
Johan Fredrik Varen

3

Một ví dụ là đối tượng với một phương pháp __toString: $str = $obj->__toString();vs $str = (string) $obj;. Việc gõ thứ hai ít hơn nhiều và phần bổ sung là dấu chấm câu, mất nhiều thời gian hơn để gõ. Tôi cũng nghĩ rằng nó dễ đọc hơn, mặc dù những người khác có thể không đồng ý.

Một là làm cho một mảng phần tử duy nhất: array($item);vs (array) $item;. Điều này sẽ đặt bất kỳ loại vô hướng (số nguyên, tài nguyên, vv) trong một mảng.
Nói chung, nếu $itemlà một đối tượng, các thuộc tính của nó sẽ trở thành khóa cho các giá trị của chúng. Tuy nhiên, tôi nghĩ rằng đối tượng-> chuyển đổi mảng hơi lạ: các thuộc tính riêng tư và được bảo vệ là một phần của mảng và được đổi tên. Để trích dẫn tài liệu PHP : các biến riêng có tên lớp được đặt trước tên biến; các biến được bảo vệ có '*' được đặt trước tên biến.

Một cách sử dụng khác là chuyển đổi dữ liệu GET / POST thành các loại thích hợp cho cơ sở dữ liệu. MySQL có thể tự xử lý việc này nhưng tôi nghĩ rằng các máy chủ tuân thủ ANSI hơn có thể từ chối dữ liệu. Lý do tôi chỉ đề cập đến cơ sở dữ liệu là trong hầu hết các trường hợp khác, dữ liệu sẽ có một thao tác được thực hiện trên đó theo loại của nó tại một số điểm (ví dụ: int / float thường sẽ có các phép tính được thực hiện trên chúng, v.v.).


Đây là những ví dụ tuyệt vời về cách thức hoạt động của kiểu đúc. Tuy nhiên, tôi không tin rằng họ đáp ứng nhu cầu . Vâng, bạn có thể chuyển đổi một đối tượng thành một mảng, nhưng tại sao? Tôi đoán bởi vì sau đó bạn có thể sử dụng vô số các hàm mảng PHP trên mảng mới, nhưng tôi không thể hiểu làm thế nào nó sẽ hữu ích. Ngoài ra, PHP thường tạo các truy vấn chuỗi để gửi đến cơ sở dữ liệu MySQL, vì vậy loại biến không liên quan (chuyển đổi chuỗi tự động từ inthoặc floatsẽ xảy ra khi xây dựng truy vấn). (array) $itemgọn gàng , nhưng hữu ích?
Stephen

Tôi thực sự đồng ý. Khi tôi gõ chúng lên, tôi nghĩ rằng tôi sẽ nghĩ ra một số cách sử dụng, nhưng tôi đã không làm thế. Đối với các công cụ cơ sở dữ liệu, nếu các tham số là một phần của chuỗi truy vấn, thì bạn đã đúng, việc truyền không có mục đích. Tuy nhiên, khi sử dụng các truy vấn tham số (luôn luôn là một ý tưởng tốt), có thể chỉ định các loại tham số.
Alan Pearce

Aha! Bạn có thể đã đạt được một lý do hợp lệ với Truy vấn tham số.
Stephen

0

Kịch bản này:

$tags = _GET['tags'];
foreach ($tags as $tag) {
    echo 'tag: ', $tag;
}

sẽ chạy tốt cho script.php?tags[]=onenhưng sẽ thất bại script.php?tags=one_GET['tags']trả về một mảng trong trường hợp đầu tiên nhưng không phải trong trường hợp thứ hai. Vì tập lệnh được viết để mong đợi một mảng (và bạn có ít quyền kiểm soát hơn chuỗi truy vấn được gửi đến tập lệnh), nên vấn đề có thể được giải quyết bằng cách truyền kết quả một cách thích hợp từ _GET:

$tags = (array) _GET['tags'];
foreach ($tags as $tag) {
    echo 'tag: ', $tag;
}

0

Nó cũng có thể được sử dụng như một phương pháp nhanh và bẩn để đảm bảo dữ liệu không đáng tin cậy sẽ không phá vỡ thứ gì đó, ví dụ như nếu sử dụng dịch vụ từ xa có xác thực tào lao và chỉ phải chấp nhận số.

$amount = (float) $_POST['amount'];

if( $amount > 0 ){
    $remoteService->doacalculationwithanumber( $amount );    
}

Rõ ràng điều này là thiếu sót và cũng được xử lý ngầm bởi toán tử so sánh trong câu lệnh if, nhưng rất hữu ích trong việc đảm bảo bạn biết chính xác mã của bạn đang làm gì.


1
Ngoại trừ việc nó không phá vỡ. Ngay cả khi $_POST['amount']chứa một chuỗi rác, php sẽ đánh giá rằng nó không lớn hơn 0. Nếu nó chứa một chuỗi đại diện cho một số dương, nó sẽ đánh giá đúng.
Stephen

1
Không hoàn toàn đúng. Xem xét số tiền $ đang được chuyển đến dịch vụ của bên thứ ba trong điều kiện phải nhận được một số. Nếu ai đó đã vượt qua trong $ _POST ['lượng'] = "100 bobbins", việc xóa (float) vẫn sẽ cho phép điều kiện vượt qua nhưng số tiền $ sẽ không phải là một con số.
Cuộc tranh chấp

-2

Một "sử dụng" các biến đúc lại PHP đang hoạt động mà tôi thường thấy khi sử dụng là khi lấy dữ liệu từ các nguồn bên ngoài (đầu vào của người dùng hoặc cơ sở dữ liệu). Nó cho phép các lập trình viên (lưu ý rằng tôi không nói các nhà phát triển) bỏ qua (hoặc thậm chí không học) các kiểu dữ liệu khác nhau có sẵn từ các nguồn khác nhau.

Một lập trình viên (lưu ý rằng tôi không nói nhà phát triển) có mã mà tôi đã kế thừa và vẫn duy trì dường như không biết rằng có sự khác biệt giữa chuỗi "20"được trả về trong $_GETsiêu biến, giữa hoạt động số nguyên20 + 20 khi cô ấy thêm nó vào giá trị trong cơ sở dữ liệu. Cô ấy chỉ may mắn khi PHP sử dụng .để nối chuỗi chứ không +giống như mọi ngôn ngữ khác, vì tôi đã thấy mã của cô ấy "thêm" hai chuỗi (một varcahrtừ MySQL và một giá trị từ $_GET) và nhận được một int.

Đây có phải là một ví dụ thực tế? Chỉ theo nghĩa là nó cho phép các lập trình viên thoát khỏi mà không biết họ đang làm việc với kiểu dữ liệu nào.Cá nhân tôi ghét nó.


2
Tôi không thấy câu trả lời này làm tăng giá trị cho cuộc thảo luận. Việc PHP cho phép một kỹ sư (hoặc lập trình viên hoặc lập trình viên, những gì có bạn) thực hiện các hoạt động toán học trên các chuỗi đã rất rõ ràng trong câu hỏi.
Stephen

Cảm ơn bạn Stephen. Có lẽ tôi đã sử dụng quá nhiều từ để nói "PHP cho phép những người không biết kiểu dữ liệu nào tạo ra các ứng dụng làm những gì họ mong đợi trong điều kiện lý tưởng".
dotancohen
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.