Ketik variabel casting di PHP, apa alasan praktis untuk melakukan ini?

45

PHP, seperti yang kita ketahui, memiliki ketikan yang lemah . Bagi yang tidak, PHP.net mengatakan:

PHP tidak memerlukan (atau mendukung) definisi tipe eksplisit dalam deklarasi variabel; tipe variabel ditentukan oleh konteks di mana variabel digunakan.

Suka atau benci, PHP membuat ulang variabel saat itu juga. Jadi, kode berikut ini valid:

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

PHP juga memungkinkan Anda untuk melemparkan variabel secara eksplisit, seperti:

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

Itu semua keren ... tetapi, untuk kehidupan saya, saya tidak dapat membayangkan alasan praktis untuk melakukan ini.

Saya tidak punya masalah dengan pengetikan yang kuat dalam bahasa yang mendukungnya, seperti Java. Tidak apa-apa, dan saya benar-benar memahaminya. Juga, saya menyadari - dan sepenuhnya memahami kegunaan - tipe mengisyaratkan dalam parameter fungsi.

Masalah yang saya miliki dengan tipe casting dijelaskan oleh kutipan di atas. Jika PHP dapat bertukar tipe sesuka hati , ia dapat melakukannya bahkan setelah Anda memaksakan suatu tipe; dan dapat melakukannya on-the-fly ketika Anda membutuhkan jenis tertentu dalam sebuah operasi. Itu membuat yang berikut ini valid:

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

Jadi apa gunanya?


Ambil contoh teoretis dari dunia ini di mana casting tipe yang ditentukan pengguna masuk akal dalam PHP :

  1. Anda memaksa variabel pemeran $foosebagai int(int)$foo.
  2. Anda mencoba untuk menyimpan nilai string dalam variabel $foo.
  3. PHP melempar pengecualian !! ← Itu masuk akal. Tiba-tiba alasan casting tipe yang ditentukan pengguna ada!

Fakta bahwa PHP akan beralih hal-hal sesuai kebutuhan membuat titik dari tipe casting yang didefinisikan pengguna tidak jelas. Misalnya, dua contoh kode berikut ini setara:

// 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;

Setahun setelah awalnya mengajukan pertanyaan ini, tebak siapa yang mendapati dirinya menggunakan typecasting di lingkungan praktis? Hormat kami.

Syaratnya adalah untuk menampilkan nilai uang di situs web untuk menu restoran. Desain situs mengharuskan agar trailing zero dipangkas, sehingga tampilan terlihat seperti berikut:

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

Cara terbaik yang saya temukan untuk melakukan itu adalah membuang variabel sebagai pelampung:

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

PHP memotong nol trailing float, dan kemudian menampilkan kembali float sebagai string untuk penggabungan.

Stephen
sumber
Ini -> $ value = $ value. 'TaDa!'; Akan mengembalikan $ value ke string sebelum melakukan penugasan ke nilai akhir $ value. Tidak terlalu mengejutkan bahwa jika Anda memaksa pemeran jenis, Anda mendapatkan pemeran tipe. Tidak yakin apa gunanya bertanya apa gunanya?
Chris
"# 3. PHP melempar pengecualian !! <--- Itu masuk akal." Sebenarnya itu tidak masuk akal sama sekali. Itu bahkan bukan masalah di Java, JavaScript atau bahasa C-sintaks lain yang saya tahu. Siapa yang waras mereka akan melihat itu sebagai perilaku yang diinginkan? Apakah Anda ingin memiliki (string)gips di mana-mana ?
Nicole
@Renesis: Anda salah paham. Yang saya maksudkan adalah bahwa pengecualian akan dibuang hanya jika pengguna telah mengetikkan variabel. Perilaku normal (di mana PHP melakukan casting untuk Anda) tentu saja tidak akan membuang pengecualian. Saya mencoba untuk mengatakan bahwa casting tipe yang ditentukan pengguna sedang diperdebatkan , tetapi jika pengecualian dilemparkan itu akan tiba-tiba masuk akal.
Stephen
Jika Anda mengatakan $intval.'bar'melempar pengecualian, saya masih tidak setuju. Itu tidak membuang pengecualian dalam bahasa apa pun. (Semua bahasa yang saya tahu melakukan pemeran otomatis atau a .toString()). Jika Anda mengatakan $intval = $stringvalmelempar pengecualian, maka Anda berbicara tentang bahasa yang sangat diketik. Saya tidak bermaksud terdengar kasar, jadi, maaf jika saya melakukannya. Saya hanya berpikir itu bertentangan dengan apa yang digunakan oleh setiap pengembang, dan jauh, jauh lebih tidak nyaman.
Nicole
@Stephen - Saya mengirim jawaban setelah beberapa penyelidikan. Hasil yang sangat menarik - saya pikir 2 kasus pasti akan menunjukkan tujuan untuk casting, tetapi PHP bahkan lebih aneh daripada yang saya kira.
Nicole

Jawaban:

32

Dalam bahasa yang diketik dengan lemah, casting tipe ada untuk menghapus ambiguitas dalam operasi yang diketik, ketika sebaliknya kompiler / juru bahasa akan menggunakan urutan atau aturan lain untuk membuat asumsi operasi yang digunakan.

Biasanya saya akan mengatakan PHP mengikuti pola ini, tetapi dari kasus-kasus yang saya periksa, PHP telah berperilaku kontra-intuitif di masing-masing.

Berikut adalah kasus-kasus itu, menggunakan JavaScript sebagai bahasa perbandingan.

String Concatentation

Jelas ini bukan masalah di PHP karena ada string concatenation ( .) dan penambahan ( +) operator yang terpisah.

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

Perbandingan String

Seringkali dalam sistem komputer "5" lebih besar dari "10" karena tidak menafsirkannya sebagai angka. Tidak demikian halnya di PHP, yang, meskipun keduanya adalah string, menyadari bahwa itu adalah angka dan menghilangkan kebutuhan untuk pemain):

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

Fungsi mengetik tanda tangan

PHP mengimplementasikan pemeriksaan tipe kosong pada tanda tangan fungsi, tapi sayangnya itu sangat cacat sehingga jarang digunakan.

Saya pikir saya mungkin melakukan sesuatu yang salah, tetapi komentar pada dokumen menegaskan bahwa tipe built-in selain array tidak dapat digunakan dalam tanda tangan fungsi PHP - meskipun pesan kesalahan menyesatkan.

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?

Dan tidak seperti bahasa lain yang saya tahu, bahkan jika Anda menggunakan tipe yang dimengerti, null tidak bisa lagi diteruskan ke argumen itu ( must be an instance of array, null given). Bodoh sekali.

Interpretasi Boolean

[ Sunting ]: Yang ini baru. Saya memikirkan kasus lain, dan sekali lagi logikanya terbalik dari 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.

Jadi sebagai kesimpulan, satu-satunya kasus berguna yang dapat saya pikirkan adalah ... (drumroll)

Ketik pemotongan

Dengan kata lain, ketika Anda memiliki nilai satu jenis (katakanlah string) dan Anda ingin menafsirkannya sebagai jenis lain (int) dan Anda ingin memaksanya untuk menjadi salah satu set nilai yang valid dalam jenis itu:

$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

Saya tidak bisa melihat upcasting (ke jenis yang mencakup lebih banyak nilai) yang benar-benar akan memberi Anda keuntungan.

Jadi, sementara saya tidak setuju dengan usulan penggunaan pengetikan (Anda pada dasarnya mengusulkan pengetikan statis , tetapi dengan ambiguitas bahwa hanya jika itu dicekokkan secara paksa ke dalam jenis pengetikan yang akan menyebabkan kesalahan - yang akan menyebabkan kebingungan), saya pikir itu bagus pertanyaan, karena casting ternyata memiliki tujuan yang sangat sedikit di PHP.

Nicole
sumber
Oke, bagaimana kalau E_NOTICEbegitu? :)
Stephen
@Stephen E_NOTICEmungkin baik-baik saja, tetapi bagi saya keadaan ambigu menyangkut - bagaimana Anda tahu dengan melihat satu bit kode jika variabel berada di negara itu (telah dilemparkan ke tempat lain)? Juga, saya menemukan kondisi lain dan menambahkannya ke jawaban saya.
Nicole
1
Sedangkan untuk evaluasi Boolean, dokumen PHP dengan jelas menyatakan apa yang dianggap salah ketika mengevaluasi ke boolean dan string kosong dan string "0" dianggap salah. Jadi bahkan ketika ini terasa aneh, itu adalah perilaku yang normal dan diharapkan.
Jacek Prucia
untuk menambahkan bit ke kebingungan: echo "010" == 010 dan echo "0x10" == 0x10;-)
vartec
1
Perhatikan bahwa pada PHP 7 , catatan jawaban ini pada petunjuk jenis skalar tidak akurat.
John V.
15

Anda sedang mencampur konsep tipe lemah / kuat dan dinamis / statis.

PHP lemah dan dinamis, tetapi masalah Anda terletak pada konsep tipe dinamis. Itu berarti, variabel tidak memiliki tipe, nilai tidak.

'Casting tipe' adalah ekspresi yang menghasilkan nilai baru dari tipe yang berbeda dari aslinya; itu tidak melakukan apa pun pada variabel (jika ada yang terlibat).

Satu situasi di mana saya secara teratur mengetikkan nilai-nilai cor adalah pada parameter SQL numerik. Anda seharusnya membersihkan / menghindar dari nilai input apa pun yang Anda masukkan ke dalam pernyataan SQL, atau (jauh lebih baik) menggunakan query parameter. Tetapi, jika Anda menginginkan beberapa nilai yang HARUS berupa bilangan bulat, jauh lebih mudah untuk hanya melemparkannya.

Mempertimbangkan:

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

jika saya meninggalkan baris pertama, $idakan menjadi vektor yang mudah untuk injeksi SQL. Para pemain memastikan bahwa itu adalah bilangan bulat yang tidak berbahaya; setiap upaya untuk memasukkan beberapa SQL hanya akan menghasilkan permintaanid=0

Javier
sumber
Saya akan menerimanya. Sekarang, sejauh kegunaan Type Casting?
Stephen
Lucu sekali Anda memunculkan injeksi SQL. Saya berdebat tentang SO dengan seseorang yang menggunakan teknik ini untuk membersihkan input pengguna. Tetapi masalah apa yang mysql_real_escape_string($id);belum diselesaikan oleh metode ini?
Stephen
ini lebih pendek :-) tentu saja, untuk string saya menggunakan query parameter, atau (jika menggunakan ekstensi mysql lama) lepas darinya.
Javier
2
mysql_real_escape_string()memiliki kerentanan untuk tidak melakukan apa pun ke string seperti '0x01ABCDEF' (yaitu representasi heksadesimal bilangan bulat). Dalam beberapa pengkodean multibyte (bukan Unicode lucklily) string seperti ini dapat digunakan untuk memecah kueri (karena akan dievaluasi oleh MySQL untuk sesuatu yang berisi penawaran). Itu sebabnya tidak mysql_real_escape_string()juga is_int()adalah pilihan terbaik untuk berurusan dengan nilai integer. Typecasting adalah.
Mchl
Tautan dengan beberapa perincian lebih lanjut: ilia.ws/archives/…
Mchl
4

Satu penggunaan untuk casting tipe dalam PHP yang saya temukan:

Saya sedang mengembangkan aplikasi android yang membuat permintaan http ke skrip PHP di server untuk mengambil data dari database. Script menyimpan data dalam bentuk objek PHP (atau array asosiatif) dan dikembalikan sebagai objek JSON ke aplikasi. Tanpa tipe casting saya akan menerima sesuatu seperti ini:

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

Tapi, menggunakan casting tipe PHP (int)pada id pengguna ketika menyimpannya objek PHP, saya malah mengembalikannya ke aplikasi:

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

Kemudian ketika objek JSON diuraikan dalam aplikasi, itu menyelamatkan saya dari keharusan mengurai id ke Integer!

Lihat, sangat bermanfaat.

Ryan
sumber
Saya belum mempertimbangkan untuk memformat data untuk dikonsumsi oleh sistem eksternal yang diketik dengan kuat. +1
Stephen
Ini terutama benar ketika berbicara JSON ke sistem eksternal seperti Elasticsearch. Sebuah json_encode () - nilai ed "5" akan memberikan hasil yang sangat berbeda dari nilai 5.
Johan Fredrik Varen
3

Salah satu contoh adalah objek dengan metode __toString: $str = $obj->__toString();vs $str = (string) $obj;. Ada lebih sedikit mengetik di yang kedua, dan hal-hal tambahan adalah tanda baca, yang membutuhkan waktu lebih lama untuk mengetik. Saya juga berpikir itu lebih mudah dibaca, meskipun yang lain mungkin tidak setuju.

Lain adalah membuat array tunggal-elemen: array($item);vs (array) $item;. Ini akan menempatkan semua jenis skalar (integer, sumber daya, dll.) Di dalam array.
Altenatif, jika $itemmerupakan objek, propertinya akan menjadi kunci nilai-nilainya. Namun, saya pikir konversi objek-> array agak aneh: properti pribadi dan dilindungi adalah bagian dari array, dan diganti namanya. Mengutip dokumentasi PHP : variabel pribadi memiliki nama kelas yang diawali dengan nama variabel; variabel yang dilindungi memiliki '*' yang diawali dengan nama variabel.

Penggunaan lain adalah mengubah data GET / POST menjadi tipe yang sesuai untuk basis data. MySQL dapat menangani ini sendiri, tetapi saya pikir server yang lebih sesuai dengan ANSI mungkin menolak data. Alasan saya hanya menyebutkan database adalah bahwa dalam kebanyakan kasus lain, data akan memiliki operasi yang dilakukan di atasnya sesuai dengan jenisnya di beberapa titik (yaitu int / mengapung biasanya akan memiliki perhitungan yang dilakukan pada mereka, dll).

Alan Pearce
sumber
Ini adalah contoh yang bagus tentang cara kerja casting tipe. Namun, saya tidak yakin bahwa mereka memenuhi kebutuhan . Ya, Anda bisa mengonversi objek ke array, tetapi mengapa? Saya kira karena Anda kemudian dapat menggunakan berbagai fungsi array PHP pada array baru, tetapi saya tidak dapat memahami bagaimana itu akan berguna. Selain itu, PHP biasanya membuat kueri string untuk dikirim ke database MySQL, sehingga tipe variabel tidak relevan (konversi string otomatis dari intatau floatakan terjadi saat membuat kueri). (array) $itemapakah rapi , tetapi bermanfaat?
Stephen
Saya sebenarnya setuju. Ketika saya mengetiknya, saya berpikir bahwa saya akan memikirkan beberapa kegunaan, tetapi saya tidak melakukannya. Untuk hal-hal basis data, jika parameter merupakan bagian dari string kueri, maka Anda benar, casting tidak memiliki tujuan. Namun, saat menggunakan kueri parametris (yang selalu merupakan ide bagus), dimungkinkan untuk menentukan tipe parameter.
Alan Pearce
Aha! Anda mungkin telah menemukan alasan yang valid dengan Pertanyaan Parameter.
Stephen
0

Skrip ini:

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

akan berjalan dengan baik untuk script.php?tags[]=onetetapi akan gagal untuk script.php?tags=one, karena _GET['tags']mengembalikan array dalam kasus pertama tetapi tidak dalam yang kedua. Karena skrip ditulis untuk mengharapkan array (dan Anda kurang memiliki kendali atas string kueri yang dikirim ke skrip), masalahnya dapat diselesaikan dengan memberikan hasil dari _GET:

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

Ini juga dapat digunakan sebagai metode cepat dan kotor untuk memastikan data yang tidak dipercaya tidak akan merusak sesuatu misalnya jika menggunakan layanan jarak jauh yang memiliki validasi omong kosong dan hanya boleh menerima angka.

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

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

Jelas ini cacat dan juga ditangani secara implisit oleh operator perbandingan dalam pernyataan if, tetapi sangat membantu dalam memastikan Anda tahu persis apa yang dilakukan kode Anda.

Gruffputs
sumber
1
Kecuali itu tidak rusak. Bahkan jika $_POST['amount']berisi string sampah, php akan mengevaluasi bahwa itu tidak lebih besar dari nol. Jika itu berisi string yang mewakili angka positif, itu akan mengevaluasi benar.
Stephen
1
Tidak sepenuhnya benar. Pertimbangkan jumlah $ sedang diteruskan ke layanan pihak ketiga di dalam kondisi yang harus menerima nomor. Jika seseorang memberikan $ _POST ['jumlah'] = "100 bobbins", menghapus (float) masih akan membiarkan syarat untuk lulus tetapi $ jumlah tidak akan menjadi angka.
Gruffputs
-2

Salah satu "penggunaan" dari variabel PHP re-casting on-the-fly yang sering saya lihat digunakan adalah ketika mengambil data dari sumber eksternal (input pengguna atau database). Ini memungkinkan para pembuat kode (perhatikan bahwa saya tidak mengatakan pengembang) mengabaikan (atau bahkan tidak belajar) tipe data berbeda yang tersedia dari sumber yang berbeda.

Satu coder (perhatikan bahwa saya tidak mengatakan pengembang) yang kodenya saya warisi dan masih pertahankan sepertinya tidak tahu bahwa ada perbedaan antara string "20"yang dikembalikan dalam $_GETvariabel super, ke antara operasi integer20 + 20 ketika dia menambahkannya ke nilai dalam database. Dia hanya beruntung bahwa PHP menggunakan .untuk penggabungan string dan tidak +seperti bahasa lainnya, karena saya telah melihat kodenya "menambahkan" dua string (a varcahrdari MySQL dan nilai dari $_GET) dan mendapatkan int.

Apakah ini contoh praktis? Hanya dalam arti bahwa itu memungkinkan coders lolos dengan tidak tahu apa tipe data yang mereka kerjakan. Saya pribadi membencinya.

dotancohen
sumber
2
Saya tidak melihat bagaimana jawaban ini menambah nilai diskusi. Fakta bahwa PHP memungkinkan seorang insinyur (atau programmer, atau coder, apa pun yang Anda miliki) untuk melakukan operasi matematika pada string sudah sangat jelas dalam pertanyaan.
Stephen
Stephen terima kasih. Saya mungkin menggunakan terlalu banyak kata untuk mengatakan "PHP memungkinkan orang yang tidak tahu apa tipe data yang membuat aplikasi yang melakukan apa yang mereka harapkan dalam kondisi ideal".
dotancohen