2010-09-12 14 views
12

Dopo alcuni lavori in C e Java sono stato sempre più infastidito dalle leggi wild west in PHP. Quello che sento davvero che PHP manchi è un tipo di dati rigido. Il fatto che string ('0') == (int) 0 == (booleano) falso è un esempio.PHP Typecasting: buono o cattivo?

Non è possibile fare affidamento su quale sia il tipo di dati restituito da una funzione. Non è possibile forzare gli argomenti di una funzione in modo che siano di un tipo specifico, il che potrebbe portare a un confronto non rigoroso con conseguente risultato imprevisto. Tutto può essere curato, ma si apre ancora a bug inaspettati.

È buona o cattiva pratica gli argomenti typecast ricevuti per un metodo? Ed è bello digitare il ritorno?

IE

public function doo($foo, $bar) { 
    $foo = (int)$foo; 
    $bar = (float)$bar; 
    $result = $bar + $foo; 
    return (array)$result; 
} 

L'esempio è abbastanza stupido e non l'ho provato, ma penso che ognuno ottiene l'idea. C'è qualche ragione per il dio PHP di convertire il tipo di dati come vuole, oltre a far sì che le persone che non conoscono i tipi di dati usano PHP?

+1

Si potrebbe pensare che la digitazione debole (come in PHP, javascript, Smalltalk, c, C++, ecc.) Sia un difetto, ma è parte del design di PHP, e quindi improbabile che cambi. È anche una delle caratteristiche che rendono PHP un linguaggio di tipo c. – GZipp

+2

@GZopp: C e C++ non sono linguaggi tipizzati debolmente. –

+0

In realtà si può richiedere che gli argomenti siano di un certo tipo, ma funziona solo con classi e matrici, non con altri tipi built-in. http://www.php.net/manual/en/functions.arguments.php#98425 – mwhite

risposta

20

bene o nel male, loose-digitazione è "The Way PHP". Molti dei built-in, e la maggior parte dei costrutti linguistici, funzioneranno su qualunque tipo li forniate - silenziosamente (e spesso pericolosamente) proiettandoli dietro le quinte per far combaciare le cose (un po ').

Venendo da una Java/C/C++ fondo me stesso, il modello loose-tipizzazione di PHP è sempre stata una fonte di frustrazione per me. Ma nel corso degli anni ho scoperto che, se devo scrivere PHP, posso fare un lavoro migliore (codice più pulito, più sicuro, più testabile) abbracciando la "scioltezza" di PHP, piuttosto che combatterlo; e io finisco per essere una scimmia più felice per questo.

Il casting è fondamentale per la mia tecnica e (IMHO) è l'unico modo per creare in modo coerente codice PHP pulito e leggibile che gestisca argomenti di tipo misto in un modo comprensibile, testabile e deterministico.

Il punto principale (che si capisce chiaramente) è che, in PHP, non si può semplicemente presumere che un argomento sia il tipo che ci si aspetta che sia. In questo modo, potresti avere gravi conseguenze che probabilmente non riuscirai a rilevare fino a quando la tua app non sarà in produzione.

Per illustrare questo punto:

<?php 

function displayRoomCount($numBoys, $numGirls) { 
    // we'll assume both args are int 

    // check boundary conditions 
    if(($numBoys < 0) || ($numGirls < 0)) throw new Exception('argument out of range'); 

    // perform the specified logic 
    $total = $numBoys + $numGirls; 
    print("{$total} people: {$numBoys} boys, and {$numGirls} girls \n"); 
} 

displayRoomCount(0, 0); // (ok) prints: "0 people: 0 boys, and 0 girls" 

displayRoomCount(-10, 20); // (ok) throws an exception 

displayRoomCount("asdf", 10); // (wrong!) prints: "10 people: asdf boys, and 10 girls" 

Un approccio per risolvere questo è quello di limitare i tipi che la funzione può accettare, un'eccezione quando viene rilevato un tipo non valido. Altri hanno già menzionato questo approccio. Appare bene alla mia estetica Java/C/C++, e ho seguito questo approccio in PHP per anni e anni. In breve, non c'è niente di sbagliato in questo, ma va contro "The PHP Way", e dopo un po 'di tempo inizia a sentirmi come un tuffo.

In alternativa, il casting fornisce un modo semplice e pulito per garantire che la funzione si comporti in modo deterministico per tutti i possibili input, senza dover scrivere una logica specifica per gestire ogni tipo diverso.

Utilizzando fusione, il nostro esempio diventa ora:

<?php 

function displayRoomCount($numBoys, $numGirls) { 
    // we cast to ensure that we have the types we expect 
    $numBoys = (int)$numBoys; 
    $numGirls = (int)$numGirls; 

    // check boundary conditions 
    if(($numBoys < 0) || ($numGirls < 0)) throw new Exception('argument out of range'); 

    // perform the specified logic 
    $total = $numBoys + $numGirls; 
    print("{$total} people: {$numBoys} boys, and {$numGirls} girls \n"); 
} 

displayRoomCount("asdf", 10); // (ok now!) prints: "10 people: 0 boys, and 10 girls" 

La funzione ora si comporta come previsto. In effetti, è facile dimostrare che il comportamento della funzione è ora ben definito per tutti gli ingressi possibili. Questo perché l'operazione di cast è ben definita per tutti i possibili input; i cast assicurano che lavoriamo sempre con i numeri interi; e il resto della funzione è scritto in modo da essere ben definito per tutti i possibili numeri interi.

Rules for type-casting in PHP are documented here, (vedere i collegamenti specifici del tipo a metà della pagina, ad esempio: "Conversione in intero").

Questo approccio ha il vantaggio aggiunto che la funzione si comporterà ora in modo coerente con altri built-in PHP e costrutti del linguaggio. Per esempio:

// assume $db_row read from a database of some sort 
displayRoomCount($db_row['boys'], $db_row['girls']); 

funzionano bene, nonostante il fatto che $db_row['boys'] e $db_row['girls'] sono in realtà le stringhe che contengono valori numerici. Ciò è coerente con il modo in cui lo sviluppatore PHP medio (che non conosce C, C++ o Java) si aspetta che funzioni.


Per quanto riguarda la fusione valori di ritorno: c'è ben poco senso farlo, a meno che non si sa di avere una variabile potenzialmente di tipo misto, e si desidera assicurarsi sempre che il valore di ritorno è un tipo specifico. Questo è più spesso il caso nei punti intermedi del codice, piuttosto che nel punto in cui si sta tornando da una funzione.

Un esempio pratico:

<?php 

function getParam($name, $idx=0) { 
    $name = (string)$name; 
    $idx = (int)$idx; 

    if($name==='') return null; 
    if($idx<0) $idx=0; 

    // $_REQUEST[$name] could be null, or string, or array 
    // this depends on the web request that came in. Our use of 
    // the array cast here, lets us write generic logic to deal with them all 
    // 
    $param = (array)$_REQUEST[$name]; 

    if(count($param) <= $idx) return null; 
    return $param[$idx]; 
} 

// here, the cast is used to ensure that we always get a string 
// even if "fullName" was missing from the request, the cast will convert 
// the returned NULL value into an empty string. 
$full_name = (string)getParam("fullName"); 

si ottiene l'idea.


ci sono un paio di gotcha di essere a conoscenza del meccanismo di fusione

  • di PHP non è abbastanza intelligente per ottimizzare la "no-op" cast. Pertanto, la trasmissione sempre causa la creazione di una copia della variabile. Nella maggior parte dei casi, questo non è un problema, ma se usi regolarmente questo approccio, dovresti tenerlo nella parte posteriore della tua mente. Per questo motivo, la trasmissione può causare problemi imprevisti con riferimenti e array di grandi dimensioni. Vedi PHP Bug Report #50894 per maggiori dettagli.

  • In PHP, un numero intero che è troppo grande (o troppo piccolo) per rappresentare come un tipo intero, verrà automaticamente rappresentato come un float (o un double, se necessario). Ciò significa che il risultato di ($big_int + $big_int) può essere effettivamente un float e, se lo si trasmette a un int, il numero risultante sarà privo di significato. Quindi, se stai costruendo funzioni che devono operare su grandi numeri interi, dovresti tenerlo a mente e probabilmente prendere in considerazione un altro approccio.


Ci scusiamo per il lungo post, ma è un argomento che ho considerato in profondità, e nel corso degli anni, ho accumulato un po 'di conoscenza (e di opinione) su di esso. Esponendolo qui, spero che qualcuno lo troverà utile.

+1

Trovato questo veramente utile - avrebbe svitato più volte se fosse possibile. – cantera

0

No, non va bene per typecast perché non si sa che cosa si dovrà alla fine. Suggerirei personalmente di utilizzare funzioni come intval(), floatval(), ecc.

+1

Come mai? O il cast avrà successo e avrai il valore corretto del tipo, altrimenti l'esecuzione fallirà (verrà lanciata un'eccezione o PHP bombarderà, a seconda della versione di PHP). C'è qualcosa che mi manca qui? –

+0

Anch'io sono curioso di sapere cosa dice Billy. intval() cuciture da fare come colare il tipo con (int) – Anders

2

È possibile utilizzare type hinting per tipi complessi. Se è necessario confrontare il valore + tipo è possibile utilizzare "===" per il confronto.

(0 === false) => results in false 
(0 == false) => results in true 

Inoltre si scrive return (array)$result; che non ha senso. Quello che vuoi in questo caso è return array($result) se vuoi che il tipo restituito sia un array.

+0

Sono consapevole dei difetti nell'esempio. È come lo farei in C, ma come dici tu sicuramente non lo farò in PHP. Era principalmente per la coerenza nell'esempio. Sono a conoscenza del tipo di suggerimento, ma non funziona per i tipi primitivi :(Sarà solo cercare la classe "int" invece – Anders

2

Non penso sia male, ma farei un ulteriore passo avanti: utilizzare type hinting per tipi complessi e generare un'eccezione se un tipo semplice non è quello atteso. In questo modo rendi i clienti consapevoli di eventuali costi/problemi con il cast (come la perdita di precisione che va da int -> float o float -> int).

il cast di matrice nel codice di cui sopra non è però fuorviante - si deve solo creare un nuovo array che contiene il valore di uno.

Che tutti Detto questo, il vostro esempio diventa sopra:

public function doo($foo, $bar) { 
    if (!is_int($foo)) throw new InvalidArgumentException(); 
    if (!is_float($bar)) throw new InvalidArgumentException(); 
    $result = $bar + $foo; 
    return array($result); 
} 
+0

Penso che questo sia un grande miglioramento per i miei pensieri. – Anders

+2

Sì, chiamare una funzione in PHP è lento, ma poiché l'ottimizzazione prematura è la radice di tutto il male non pensare a questo rallentamento minore e non importante, ma pensa alla leggibilità e alla manutenibilità del tuo codice;) – NikiC

+0

@Anders: Sì, lì è un sovraccarico, ma non lo ritengo significativo qui. PHP deve controllare il tipo in ogni caso per eseguire il cast in ogni caso. Naturalmente, il controllo non è gratuito. –

4

La prossima versione di PHP (probabilmente 5.4) will support scalar type hinting in arguments.

Ma a parte questo: conversione del tipo dinamico non è davvero qualcosa che si dovrebbe odiare ed evitare. Principalmente funzionerà come previsto. E se non lo fa, risolvere il problema verificando che is_* di un certo tipo, utilizzando il confronto rigoroso, ..., ...

+2

Non sono sicuro che sia stato concordato (quindi di nuovo, su php.internals ci vogliono solo 2 persone che parlano in privato per organizzare un rilascio). Mentre c'è una patch per questo non sono sicuro che sia stato concordato che sarebbe in PHP. – Ross

+0

Err .. php supporta già il tipo hinting (e ha un po 'di tempo). –

+0

@Billy: non supportava suggerimenti tipo scalari e questo è ciò di cui stiamo parlando qui. – NikiC

Problemi correlati