Digitare le variabili di casting in PHP, qual è la ragione pratica per farlo?


45

PHP, come molti di noi sanno, ha una digitazione debole . Per coloro che non lo fanno, PHP.net dice:

PHP non richiede (o supporta) la definizione esplicita del tipo nella dichiarazione delle variabili; il tipo di una variabile è determinato dal contesto in cui viene utilizzata la variabile.

Lo adori o lo odi, PHP rilancia le variabili al volo. Quindi, il seguente codice è valido:

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

PHP ti consente anche di lanciare esplicitamente una variabile, in questo modo:

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

È tutto fantastico ... ma, per la mia vita, non riesco a concepire una ragione pratica per farlo.

Non ho problemi con la digitazione forte in lingue che lo supportano, come Java. Va bene, e lo capisco perfettamente. Inoltre, sono a conoscenza di - e comprendo appieno l'utilità - suggerimenti di tipo nei parametri di funzione.

Il problema che ho con il tipo casting è spiegato dalla citazione sopra. Se PHP può scambiare i tipi a piacimento , può farlo anche dopo aver forzato il cast di un tipo; e può farlo al volo quando hai bisogno di un certo tipo in un'operazione. Ciò rende valido quanto segue:

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

Quindi qual è il punto?


Prendi questo esempio teorico di un mondo in cui il cast di tipi definiti dall'utente ha senso in PHP :

  1. Forzate la variabile di cast $foocome int(int)$foo.
  2. Si tenta di memorizzare un valore di stringa nella variabile $foo.
  3. PHP genera un'eccezione !! ← Questo avrebbe senso. Improvvisamente esiste la ragione per il cast di tipi definiti dall'utente!

Il fatto che PHP cambierà le cose secondo necessità rende vago il punto del cast di tipo definito dall'utente. Ad esempio, i seguenti due esempi di codice sono equivalenti:

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

Un anno dopo aver inizialmente posto questa domanda, indovina chi si è trovato a usare il typecasting in un ambiente pratico? Cordialmente.

Il requisito era di visualizzare i valori in denaro su un sito Web per un menu di ristorante. Il design del sito richiedeva che gli zeri finali fossero tagliati, in modo che il display fosse simile al seguente:

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

Il modo migliore che ho trovato per farlo è sprecare per lanciare la variabile come float:

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

PHP ritaglia gli zeri finali del float, quindi riformula il float come stringa per la concatenazione.


Questo -> $ valore = $ valore. 'TaDa!'; Trasmettere $ value alla stringa prima di eseguire l'assegnazione al valore finale di $ value. Non è davvero una sorpresa che se si forza un cast di tipo si ottiene un cast di tipo. Non sei sicuro di quale sia il punto nel chiedere qual è il punto?
Chris,

"# 3. PHP genera un'eccezione !! <--- Avrebbe senso." In realtà non avrebbe affatto senso. Questo non è nemmeno un problema in Java, JavaScript o in qualsiasi altro linguaggio di sintassi C che conosco. Chi nella loro mente giusta lo vedrebbe come un comportamento desiderabile? Vuoi avere (string)cast dappertutto ?
Nicole,

@Renesis: mi hai frainteso. Quello che intendevo dire era che un'eccezione verrebbe lanciata solo se un utente avesse lanciato una variabile di tipo. Il comportamento normale (dove PHP esegue il casting per te) ovviamente non genererebbe un'eccezione. Sto cercando di dire che il cast di tipo definito dall'utente è controverso , ma se dovesse essere lanciata un'eccezione avrebbe improvvisamente senso.
Stephen,

Se stai dicendo che $intval.'bar'genera un'eccezione, non sono ancora d'accordo. Ciò non costituisce un'eccezione in nessuna lingua. (Tutte le lingue che conosco eseguono un cast automatico o a .toString()). Se stai dicendo che $intval = $stringvalgenera un'eccezione, stai parlando di un linguaggio fortemente tipizzato. Non intendevo sembrare scortese, quindi scusami se l'ho fatto. Penso solo che vada contro ciò a cui ogni sviluppatore è abituato, ed è molto, molto meno conveniente.
Nicole,

@Stephen - Ho inviato una risposta dopo alcune indagini. Risultati davvero interessanti - Ho pensato che 2 dei casi avrebbero sicuramente mostrato uno scopo per il casting, ma PHP è ancora più strano di quanto pensassi.
Nicole,

Risposte:


32

In un linguaggio debolmente tipizzato, esiste un tipo di casting per rimuovere l'ambiguità nelle operazioni tipizzate, quando altrimenti il ​​compilatore / interprete userebbe l'ordine o altre regole per ipotizzare quale operazione utilizzare.

Normalmente direi che PHP segue questo schema, ma dei casi che ho verificato, PHP si è comportato in modo contro intuitivo in ciascuno di essi.

Ecco questi casi, utilizzando JavaScript come linguaggio di confronto.

Concatenazione di stringhe

Ovviamente questo non è un problema in PHP perché ci sono operatori di concatenazione ( .) e addizione ( +) separati .

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

Confronto delle stringhe

Spesso nei sistemi informatici "5" è maggiore di "10" perché non lo interpreta come un numero. Non così in PHP, che, anche se entrambi sono stringhe, si rende conto che sono numeri e rimuove la necessità di un cast):

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

Digitazione della firma della funzione

PHP implementa un controllo del tipo bare-bones sulle firme delle funzioni, ma sfortunatamente è così imperfetto che probabilmente è raramente utilizzabile.

Ho pensato che potessi fare qualcosa di sbagliato, ma un commento sui documenti conferma che i tipi predefiniti diversi dall'array non possono essere utilizzati nelle firme delle funzioni PHP, sebbene il messaggio di errore sia fuorviante.

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?

E a differenza di qualsiasi altra lingua che conosco, anche se usi un tipo che capisce, null non può più essere passato a quell'argomento ( must be an instance of array, null given). Che stupido.

Interpretazione booleana

[ Modifica ]: questo è nuovo. Ho pensato a un altro caso, e di nuovo la logica è invertita da 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.

Quindi in conclusione, l'unico caso utile che mi viene in mente è ... (rullo di tamburi)

Digitare troncamento

In altre parole, quando si dispone di un valore di un tipo (dire stringa) e si desidera interpretarlo come un altro tipo (int) e si desidera forzarlo a diventare uno dei set di valori validi in quel tipo:

$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

Non riesco a vedere quale upcasting (a un tipo che comprende più valori) ti guadagnerebbe davvero.

Quindi, mentre non sono d'accordo con il tuo uso proposto della digitazione (essenzialmente stai proponendo la tipizzazione statica , ma con l'ambiguità che solo se fosse forzato in un tipo genererebbe un errore - che causerebbe confusione), penso che sia un buon domanda, perché a quanto pare la fusione ha molto a ben poco in PHP.


Okay, che ne dici di un E_NOTICEallora? :)
Stephen,

@Stephen E_NOTICEpotrebbe essere ok, ma per me riguarda lo stato ambiguo: come potresti sapere guardando un bit di codice se la variabile fosse in quello stato (essendo stata lanciata da qualche altra parte)? Inoltre, ho trovato un'altra condizione e l'ho aggiunta alla mia risposta.
Nicole,

1
Per quanto riguarda la valutazione booleana, i documenti PHP dichiarano chiaramente ciò che è considerato falso quando si valuta booleana e sia la stringa vuota che una stringa "0" sono considerate false. Quindi, anche quando questo sembra bizzarro, è un comportamento normale e previsto.
Jacek Prucia,

per aggiungere un po 'di confusione: echo "010" == 010 e echo "0x10" == 0x10;-)
vartec

1
Si noti che a partire da PHP 7 , le note di questa risposta sul suggerimento di tipo scalare sono imprecise.
John V.

15

Stai mescolando i concetti di tipo debole / forte e dinamico / statico.

PHP è debole e dinamico, ma il tuo problema è con il concetto di tipo dinamico. Ciò significa che le variabili non hanno un tipo, i valori sì.

Un "tipo casting" è un'espressione che produce un nuovo valore di un diverso tipo di originale; non fa nulla alla variabile (se ne è coinvolta una).

L'unica situazione in cui scrivo regolarmente valori cast è su parametri SQL numerici. Dovresti disinfettare / sfuggire a qualsiasi valore di input che inserisci in istruzioni SQL o (molto meglio) utilizzare query con parametri. Ma, se vuoi un valore che DEVE essere un numero intero, è molto più semplice semplicemente lanciarlo.

Ritenere:

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

se avessi lasciato fuori la prima riga, $idsarebbe un vettore facile per l'iniezione SQL. Il cast si assicura che sia un numero intero innocuo; qualsiasi tentativo di inserire un po 'di SQL comporterebbe semplicemente una query perid=0


Lo accetterò. Ora, per quanto riguarda l'utilità di Casting di tipo?
Stephen,

È divertente che tu faccia apparire l'iniezione SQL. Stavo discutendo su SO con qualcuno che utilizza questa tecnica per disinfettare l'input dell'utente. Ma quale problema risolve questo metodo che mysql_real_escape_string($id);non lo fa già?
Stephen,

è più breve :-) ovviamente, per le stringhe uso query con parametri o (se uso la vecchia estensione mysql) scappo.
Javier,

2
mysql_real_escape_string()ha una vulnerabilità di non fare nulla a stringhe come '0x01ABCDEF' (ovvero rappresentazione esadecimale di un numero intero). In alcune codifiche multibyte (non fortunatamente Unicode) una stringa come questa può essere utilizzata per interrompere la query (perché viene valutata da MySQL in qualcosa che contiene un preventivo). È per questo che né mysql_real_escape_string()is_int()è la scelta migliore per trattare con valori interi. La tipografia è.
Mchl

Un link con qualche dettaglio in più: ilia.ws/archives/…
Mchl

4

Un uso per il cast di tipi in PHP che ho trovato:

sto sviluppando un'app Android che fa richieste http agli script PHP su un server per recuperare i dati da un database. Lo script memorizza i dati sotto forma di un oggetto PHP (o array associativo) e viene restituito come oggetto JSON all'app. Senza il cast del tipo riceverei qualcosa del genere:

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

Ma, usando il cast di tipo PHP sull'ID (int)dell'utente durante la memorizzazione dell'oggetto PHP, ottengo invece questo restituito all'app:

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

Quindi quando l'oggetto JSON viene analizzato nell'app, mi evita di dover analizzare l'id in un numero intero!

Vedi, molto utile.


Non avevo preso in considerazione la formattazione dei dati da utilizzare per sistemi esterni di tipo forte. +1
Stephen,

Ciò è particolarmente vero quando si parla di JSON con sistemi esterni come Elasticsearch. Un valore json_encode () - ed "5" darà risultati molto diversi rispetto al valore 5.
Johan Fredrik Varen,

3

Un esempio è oggetti con un metodo __toString: $str = $obj->__toString();vs $str = (string) $obj;. La seconda è molto meno digitata e l'elemento extra è la punteggiatura, che impiega più tempo a scrivere. Penso anche che sia più leggibile, anche se altri potrebbero non essere d'accordo.

Un altro sta facendo una matrice a elemento singolo: array($item);vs (array) $item;. Ciò inserirà qualsiasi tipo scalare (intero, risorsa, ecc.) All'interno di un array.
In alternativa, se $itemè un oggetto, le sue proprietà diventeranno chiavi dei loro valori. Tuttavia, penso che la conversione oggetto-> array sia un po 'strana: le proprietà private e protette fanno parte dell'array e vengono rinominate. Per citare la documentazione di PHP : le variabili private hanno il nome della classe anteposto al nome della variabile; le variabili protette hanno un '*' anteposto al nome della variabile.

Un altro uso è convertire i dati GET / POST in tipi appropriati per un database. MySQL può gestirlo da solo, ma penso che i server più conformi ANSI potrebbero rifiutare i dati. Il motivo per cui menziono solo i database è che nella maggior parte degli altri casi, i dati avranno un'operazione eseguita su di essi in base al loro tipo ad un certo punto (cioè int / float di solito eseguiranno calcoli su di essi, ecc.).


Questi sono ottimi esempi di come funziona la fusione di tipi. Tuttavia, non sono convinto che soddisfino un bisogno . Sì, puoi convertire un oggetto in un array, ma perché? Suppongo che potresti quindi utilizzare la miriade di funzioni di array PHP sul nuovo array, ma non riesco a capire come sarebbe utile. Inoltre, PHP di solito crea query di stringa da inviare a un database MySQL, quindi il tipo di variabile è irrilevante (la conversione automatica della stringa da into floatavverrà durante la creazione della query). (array) $itemè pulito , ma utile?
Stephen,

In realtà sono d'accordo. Mentre li scrivevo, ho pensato che avrei pensato ad alcuni usi, ma non l'ho fatto. Per quanto riguarda il database, se i parametri fanno parte della stringa di query, allora hai ragione, il casting non ha scopo. Tuttavia, quando si utilizzano query parametrizzate (che è sempre una buona idea), è possibile specificare i tipi di parametri.
Alan Pearce,

Aha! Potresti aver riscontrato un motivo valido con le query con parametri.
Stephen,

0

Questo script:

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

funzionerà bene per script.php?tags[]=onema fallirà script.php?tags=one, perché _GET['tags']restituisce un array nel primo caso ma non nel secondo. Poiché lo script è scritto in modo da prevedere un array (e hai meno controllo sulla stringa di query inviata allo script), il problema può essere risolto eseguendo il cast in modo appropriato il risultato da _GET:

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

0

Può anche essere utilizzato come metodo rapido e sporco per garantire che i dati non attendibili non rompano qualcosa, ad esempio se si utilizza un servizio remoto che ha una convalida della merda e deve accettare solo numeri.

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

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

Ovviamente questo è imperfetto e gestito implicitamente dall'operatore di confronto nell'istruzione if, ma è utile per assicurarti di sapere esattamente cosa sta facendo il tuo codice.


1
Solo che non si rompe. Anche se $_POST['amount']contenesse una stringa di immondizia, php valuterebbe che non era maggiore di zero. Se contenesse una stringa che rappresentava un numero positivo, valuterebbe vero.
Stephen,

1
Non del tutto vero. Si consideri che l'importo di $ viene passato a un servizio di terze parti all'interno del condizionale che deve ricevere un numero. Se qualcuno dovesse passare $ _POST ['amount'] = "100 bobine", la rimozione (float) consentirebbe comunque il passaggio del condizionale, ma $ amount non sarebbe un numero.
Gruffputs

-2

Un "uso" delle variabili di re-casting PHP al volo che vedo in uso spesso è quando si recuperano dati da fonti esterne (input dell'utente o database). Permette ai programmatori (nota che non ho detto sviluppatori) di ignorare (o nemmeno imparare) i diversi tipi di dati disponibili da diverse fonti.

Un programmatore (nota che non ho detto sviluppatore) il cui codice ho ereditato e mantengo ancora non sembra sapere che esiste una differenza tra la stringa "20"restituita nella $_GETsuper variabile, tra l'operazione intera20 + 20 quando la aggiunge a il valore nel database. È solo fortunata che PHP usi .per la concatenazione di stringhe e non +come ogni altra lingua, perché ho visto il suo codice "aggiungere" due stringhe (una varcahrda MySQL e un valore da $_GET) e ottenere un int.

È un esempio pratico? Solo nel senso che consente ai programmatori di cavarsela senza sapere con quali tipi di dati stanno lavorando. Lo odio personalmente.


2
Non vedo come questa risposta aggiunga valore alla discussione. Il fatto che PHP consenta a un ingegnere (o programmatore o programmatore, che cosa hai) di eseguire operazioni matematiche sulle stringhe è già ampiamente chiaro nella domanda.
Stephen,

Grazie Stefano. Forse ho usato troppe parole per dire "PHP consente alle persone che non sanno cosa sia un tipo di dati di creare applicazioni che fanno ciò che si aspettano in condizioni ideali".
dotancohen,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.