Wpisz zmienne rzutowania w PHP, jaki jest praktyczny powód tego?

45

PHP, jak większość z nas wie, ma słabe pisanie . Dla tych, którzy tego nie robią, PHP.net mówi:

PHP nie wymaga (ani nie obsługuje) jawnej definicji typu w deklaracji zmiennej; typ zmiennej jest określony przez kontekst, w którym zmienna jest używana.

Uwielbiam to lub nienawidzę, PHP ponownie rzuca zmienne w locie. Tak więc następujący kod jest prawidłowy:

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

PHP pozwala również jawnie rzutować zmienną, na przykład:

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

To wszystko fajnie ... ale przez całe życie nie mogę wymyślić praktycznego powodu, aby to zrobić.

Nie mam problemu z mocnym pisaniem w językach, które go obsługują, takich jak Java. W porządku i całkowicie to rozumiem. Jestem także świadom - i w pełni rozumiem przydatność - podpowiedzi typu w parametrach funkcji.

Problem, który mam z rzutowaniem typu, jest wyjaśniony powyższym cytatem. Jeśli PHP może zamienić typy at-woli , może to zrobić nawet po zmuszają cast typu; i może to robić w locie, gdy potrzebujesz określonego typu operacji. To sprawia, że ​​następujące są prawidłowe:

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

Więc o co chodzi?


Weź ten teoretyczny przykład świata, w którym rzutowanie typu zdefiniowane przez użytkownika ma sens w PHP :

  1. Zmuszasz rzutowaną zmienną $foojako int(int)$foo.
  2. Próbujesz zapisać wartość ciągu w zmiennej $foo.
  3. PHP zgłasza wyjątek !! ← To miałoby sens. Nagle istnieje przyczyna rzutowania typu zdefiniowanego przez użytkownika!

Fakt, że PHP będzie zmieniać rzeczy w zależności od potrzeb, powoduje, że punkt definiowania typu zdefiniowanego przez użytkownika jest niejasny. Na przykład następujące dwa przykłady kodu są równoważne:

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

Rok po tym, jak pierwotnie zadałem to pytanie, zgadnij, kto używał rzutowania czcionek w praktycznym środowisku? Z poważaniem.

Wymaganiem było wyświetlanie wartości pieniężnych na stronie internetowej dla menu restauracji. Projekt witryny wymagał przycięcia końcowych zer, aby wyświetlacz wyglądał mniej więcej tak:

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

Najlepszy sposób, w jaki udało mi się to zrobić, aby rzutować zmienną jako zmiennoprzecinkową:

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

PHP przycina końcowe zera pływaka, a następnie przekształca go jako ciąg do konkatenacji.

Stephen
źródło
To -> $ wartość = wartość $. „TaDa!”; Rzuciłbym wartość $ z powrotem na łańcuch przed wykonaniem przypisania do końcowej wartości $ wartość. Nic dziwnego, że jeśli wymusisz rzut typem, otrzymasz rzut typem. Nie jesteś pewien, o co chodzi pytając, o co w tym chodzi?
Chris
„# 3. PHP zgłasza wyjątek !! <--- To miałoby sens.” W rzeczywistości nie miałoby to żadnego sensu. Nie jest to nawet problem w Javie, JavaScript ani żadnym innym języku składni C, który znam. Kto przy zdrowych zmysłach uzna to za pożądane zachowanie? Czy chcesz mieć wszędzie(string) casty ?
Nicole,
@Reneesis: źle mnie zrozumiałeś. Miałem na myśli to, że wyjątek zostanie zgłoszony tylko wtedy, gdy użytkownik przerzuci zmienną na typ. Normalne zachowanie (gdzie PHP wykonuje rzutowanie za ciebie) oczywiście nie zgłasza wyjątku. Próbuję powiedzieć, że rzutowanie typu zdefiniowanego przez użytkownika jest dyskusyjne , ale gdyby został zgłoszony wyjątek, nagle miałoby to sens.
Stephen
Jeśli mówisz, że $intval.'bar'zgłasza wyjątek, nadal się nie zgadzam. Nie rzuca to wyjątku w żadnym języku. (Wszystkie języki, które znam, przeprowadzają automatyczną obsadę lub a .toString()). Jeśli mówisz, że $intval = $stringvalzgłasza wyjątek, to mówisz o silnie napisanym języku. Nie chciałem brzmieć niegrzecznie, więc przepraszam, jeśli tak zrobiłem. Wydaje mi się, że jest to sprzeczne z tym, do czego jest przyzwyczajony każdy programista, i jest o wiele, znacznie mniej wygodne.
Nicole,
@ Stephen - opublikowałem odpowiedź po pewnym dochodzeniu. Naprawdę ciekawe wyniki - myślałem, że 2 przypadki na pewno pokażą cel castingu, ale PHP jest jeszcze bardziej dziwny, niż myślałem.
Nicole,

Odpowiedzi:

32

W języku słabo typowanym istnieje rzutowanie w celu usunięcia niejednoznaczności w operacjach na maszynie, w innym przypadku kompilator / tłumacz użyłby kolejności lub innych reguł, aby założyć, której operacji użyć.

Normalnie powiedziałbym, że PHP podąża za tym wzorcem, ale w przypadkach, które sprawdziłem, PHP zachowywał się w każdym z nich intuicyjnie.

Oto te przypadki, w których JavaScript jest używany jako język porównawczy.

Łączenie ciągów

Oczywiście nie jest to problem w PHP, ponieważ istnieją osobne operatory konkatenacji ( .) i dodawania ( +).

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

Porównanie ciągów

Często w systemach komputerowych „5” jest większe niż „10”, ponieważ nie interpretuje go jako liczby. Nie w PHP, który, nawet jeśli oba są łańcuchami, zdaje sobie sprawę, że są liczbami i eliminuje potrzebę rzutowania):

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

Wpisywanie podpisów funkcji

PHP implementuje sprawdzanie typu podpisów funkcji od podstaw, ale niestety jest tak wadliwe, że prawdopodobnie rzadko jest użyteczne.

Myślałem, że robię coś złego, ale komentarz do dokumentacji potwierdza, że ​​wbudowanych typów innych niż tablica nie można używać w podpisach funkcji PHP - chociaż komunikat o błędzie wprowadza w błąd.

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?

I w przeciwieństwie do jakiegokolwiek innego języka, który znam, nawet jeśli użyjesz typu, który on rozumie, wartości null nie można już przekazać do tego argumentu ( must be an instance of array, null given). Jak głupio.

Interpretacja boolowska

[ Edytuj ]: ten jest nowy. Pomyślałem o innym przypadku i znowu logika jest odwrócona od 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.

Podsumowując, jedynym przydatnym przypadkiem, o którym mogę pomyśleć, jest ... (bęben)

Wpisz obcinanie

Innymi słowy, jeśli masz wartość jednego typu (powiedz ciąg znaków) i chcesz zinterpretować ją jako inny typ (int) i chcesz zmusić ją, aby stała się jednym z poprawnych zestawów wartości tego typu:

$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

Nie widzę, co tak naprawdę zyskałoby upcasting (do typu, który obejmuje więcej wartości).

Tak więc, chociaż nie zgadzam się z proponowanym przez ciebie pisaniem (zasadniczo proponujesz pisanie statyczne , ale z dwuznacznością, że tylko jeśli zostanie ono wrzucone na siłę, spowoduje błąd - co spowodowałoby zamieszanie), myślę, że to dobre pytanie, ponieważ najwyraźniej casting ma bardzo mało celu w PHP.

Nicole
źródło
Okej, E_NOTICEa może wtedy? :)
Stephen
@ Stephen E_NOTICEmoże być w porządku, ale dla mnie ten dwuznaczny stan dotyczy - skąd byś wiedział, patrząc na jeden kawałek kodu, gdyby zmienna była w tym stanie (została rzucona gdzie indziej)? Znalazłem też inny warunek i dodałem go do mojej odpowiedzi.
Nicole,
1
Jeśli chodzi o ocenę boolowską, dokumenty PHP jasno określają, co jest uważane za fałszywe, gdy ocenia się na wartość logiczną, a zarówno pusty ciąg, jak i ciąg „0” są uważane za fałszywe. Nawet jeśli wydaje się to dziwne, jest to normalne i oczekiwane zachowanie.
Jacek Prucia,
dodać nieco zamieszania: echo "010" == 010 i echo "0x10" == 0x10;-)
vartec
1
Zauważ, że od PHP 7 uwagi tej odpowiedzi dotyczące podpowiedzi typu skalarnego są niedokładne.
John V.
15

Miksujesz koncepcje typu słabego / silnego i dynamicznego / statycznego.

PHP jest słaby i dynamiczny, ale twój problem dotyczy koncepcji typu dynamicznego. Oznacza to, że zmienne nie mają typu, wartości mają.

„Rzutowanie typu” jest wyrażeniem, które tworzy nową wartość innego typu oryginału; nie robi nic ze zmienną (jeśli jest zaangażowana).

Jedyną sytuacją, w której regularnie wpisuję wartości rzutowania, są liczbowe parametry SQL. Powinieneś zdezynfekować / uciec od dowolnej wartości wejściowej wstawionej do instrukcji SQL lub (znacznie lepiej) użyć sparametryzowanych zapytań. Ale jeśli chcesz jakąś wartość, która MUSI być liczbą całkowitą, o wiele łatwiej jest ją po prostu rzucić.

Rozważać:

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

gdybym pominął pierwszą linię, $idbyłby to łatwy wektor do wstrzyknięcia SQL. Obsada upewnia się, że jest to nieszkodliwa liczba całkowita; każda próba wstawienia jakiegoś SQL spowodowałaby po prostu zapytanie oid=0

Javier
źródło
Zaakceptuję to. A teraz, jeśli chodzi o użyteczność rzutowania typu?
Stephen
To zabawne, że wywołujesz zastrzyk SQL. Kłóciłem się o SO z kimś, kto używa tej techniki do odkażania danych wprowadzanych przez użytkownika. Ale jaki problem rozwiązuje ta metoda, mysql_real_escape_string($id);ale jeszcze nie?
Stephen
jest krótszy :-) oczywiście, dla ciągów używam sparametryzowanych zapytań lub (jeśli korzystam ze starego rozszerzenia mysql) unikam go.
Javier,
2
mysql_real_escape_string()ma lukę polegającą na tym, że nic nie robi na ciągach takich jak „0x01ABCDEF” (tj. szesnastkowa reprezentacja liczby całkowitej). W niektórych kodowaniach wielobajtowych (nie na szczęście w Unicode) taki ciąg może być użyty do przerwania zapytania (ponieważ jest oceniany przez MySQL na coś, co zawiera cytat). Dlatego ani mysql_real_escape_string()nie is_int()jest najlepszym wyborem do radzenia sobie z wartościami całkowitymi. Rzutowanie jest.
Mchl
Link z kilkoma szczegółami: ilia.ws/archives/...
Mchl
4

Znalazłem jedno zastosowanie do rzutowania typu w PHP:

Tworzę aplikację na Androida, która wysyła żądania HTTP do skryptów PHP na serwerze w celu pobrania danych z bazy danych. Skrypt przechowuje dane w postaci obiektu PHP (lub tablicy asocjacyjnej) i jest zwracany jako obiekt JSON do aplikacji. Bez typu casting otrzymałbym coś takiego:

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

Ale używając rzutowania typu PHP (int)na identyfikator użytkownika podczas przechowywania go w obiekcie PHP, otrzymuję to zamiast tego:

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

Następnie, gdy obiekt JSON jest analizowany w aplikacji, ratuje mnie to przed analizowaniem identyfikatora na liczbę całkowitą!

Zobacz, bardzo przydatne.

Ryan
źródło
Nie zastanawiałem się nad formatowaniem danych dla zewnętrznych systemów o silnym typie. +1
Stephen
Jest to szczególnie prawdziwe, gdy rozmawiasz JSON z zewnętrznymi systemami, takimi jak Elasticsearch. Json_encode () - edytowana wartość „5” da zupełnie inne wyniki niż wartość 5.
Johan Fredrik Varen
3

Jednym z przykładów są przedmioty z metodą __toString: $str = $obj->__toString();vs $str = (string) $obj;. W drugim jest znacznie mniej pisania, a dodatkowym elementem jest interpunkcja, która trwa dłużej. Myślę też, że jest bardziej czytelny, chociaż inni mogą się nie zgodzić.

Innym czyni układ jednoelementową: array($item);vs (array) $item;. Spowoduje to umieszczenie dowolnego typu skalarnego (liczba całkowita, zasób itp.) W tablicy.
Alternatywnie, jeśli $itemjest obiektem, jego właściwości staną się kluczami do jego wartości. Myślę jednak, że konwersja obiekt-> tablica jest nieco dziwna: prywatne i chronione właściwości są częścią tablicy i zmieniono jej nazwę. Cytując dokumentację PHP : zmienne prywatne mają nazwę klasy dodaną do nazwy zmiennej; zmienne chronione mają znak „*” dodany do nazwy zmiennej.

Innym zastosowaniem jest konwersja danych GET / POST na odpowiednie typy dla bazy danych. MySQL może sobie z tym poradzić, ale myślę, że serwery bardziej zgodne z ANSI mogą odrzucić dane. Powodem, dla którego wspomniałem tylko o bazach danych, jest to, że w większości innych przypadków na danych zostanie wykonana operacja zgodnie z ich rodzajem w pewnym momencie (tj. Int / floats zwykle będą miały na nich obliczenia itp.).

Alan Pearce
źródło
Są to świetne przykłady działania rzutowania typu. Jednak nie jestem przekonany, że zaspokajają potrzebę . Tak, możesz przekonwertować obiekt na tablicę, ale dlaczego? Chyba dlatego, że mógłbyś wtedy użyć niezliczonych funkcji tablicy PHP na nowej tablicy, ale nie mogę pojąć, jak by to było przydatne. Ponadto PHP zwykle tworzy zapytania łańcuchowe, aby wysłać je do bazy danych MySQL, więc typ zmiennej nie ma znaczenia (automatyczna konwersja łańcucha z intlub floatnastąpi podczas budowania zapytania). (array) $itemjest schludny , ale przydatny?
Stephen
Właściwie to zgadzam się. Podczas ich pisania myślałem, że wymyślę jakieś zastosowania, ale nie zrobiłem tego. Jeśli chodzi o bazę danych, jeśli parametry są częścią ciągu zapytania, masz rację, rzutowanie nie ma sensu. Jednak przy użyciu zapytań parametryzowanych (co jest zawsze dobrym pomysłem) można określić typy parametrów.
Alan Pearce,
Aha! Być może trafiłeś prawidłowy powód za pomocą zapytań sparametryzowanych.
Stephen
0

Ten skrypt:

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

będzie działać poprawnie, script.php?tags[]=oneale nie powiedzie się script.php?tags=one, ponieważ _GET['tags']zwraca tablicę w pierwszym przypadku, ale nie w drugim. Ponieważ skrypt został napisany w taki sposób, aby oczekiwał tablicy (i masz mniejszą kontrolę nad ciągiem zapytania wysłanym do skryptu), problem można rozwiązać, odpowiednio rzutując wynik z _GET:

$tags = (array) _GET['tags'];
foreach ($tags as $tag) {
    echo 'tag: ', $tag;
}
Beldaz
źródło
0

Może być również użyty jako szybka i brudna metoda, aby zapewnić, że niezaufane dane nie spowodują uszkodzenia, np. Jeśli używasz zdalnej usługi, która ma sprawdzanie poprawności crap i musi akceptować tylko liczby.

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

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

Oczywiście jest to wadliwe i również domyślnie obsługiwane przez operator porównania w instrukcji if, ale jest pomocne w upewnieniu się, że dokładnie wiesz, co robi twój kod.

Narzekania
źródło
1
Tyle że nie pęka. Nawet jeśli $_POST['amount']zawiera ciąg śmieci, php oceni, że nie jest on większy niż zero. Gdyby zawierał ciąg reprezentujący liczbę dodatnią, oceniałby to prawda.
Stephen
1
Nie do końca prawda. Rozważ, że kwota $ jest przekazywana do usługi strony trzeciej w ramach warunku, który musi otrzymać numer. Gdyby ktoś przekazał $ _POST ['kwota'] = "100 szpulek", usunięcie (liczba zmiennoprzecinkowa) nadal pozwoliłoby na przesłanie warunku, ale $ kwota nie byłaby liczbą.
Gruffputs
-2

Jednym z „zastosowań” zmiennych PHP rzutowanych w locie, które często widzę w użyciu, są podczas pobierania danych ze źródeł zewnętrznych (dane wejściowe użytkownika lub baza danych). Pozwala programistom (zauważ, że nie powiedziałem, że programiści) ignorować (a nawet nie uczyć się) różne typy danych dostępne z różnych źródeł.

Jeden programista (zauważ, że nie powiedziałem programisty), którego kod odziedziczyłem i nadal utrzymuję, nie wie, że istnieje różnica między łańcuchem "20"zwracanym w $_GETsuper zmiennej, a operacją całkowitą,20 + 20 gdy dodaje go do wartość w bazie danych. Ma tylko szczęście, że PHP używa .do łączenia łańcuchów, a nie +jak każdy inny język, ponieważ widziałem, jak jej kod „dodaje” dwa ciągi ( varcahrz MySQL i wartość z $_GET) i dostaje int.

Czy to praktyczny przykład? Tylko w tym sensie, że pozwala programistom uciec od niewiedzy, z którymi typami danych pracują. Ja osobiście tego nienawidzę.

dotancohen
źródło
2
Nie rozumiem, w jaki sposób ta odpowiedź dodaje wartości do dyskusji. Fakt, że PHP pozwala inżynierom (lub programistom lub programistom, co masz) wykonywać operacje matematyczne na łańcuchach, jest już całkowicie jasne w pytaniu.
Stephen
Dziękuję Stephen. Być może użyłem zbyt wielu słów, aby powiedzieć „PHP pozwala osobom, które nie wiedzą, co to za typ danych, tworzyć aplikacje, które robią to, czego oczekują w idealnych warunkach”.
dotancohen