2010-09-12 17 views
12

Después de un trabajo en C y Java, me he sentido cada vez más molesto por las leyes de wild west en PHP. Lo que realmente siento es que PHP carece de tipos de datos estrictos. El hecho de que string ('0') == (int) 0 == (booleano) false sea un ejemplo.PHP Typecasting - ¿Bueno o malo?

No puede confiar en el tipo de datos que devuelve una función. Tampoco puede forzar los argumentos de una función para que sean de un tipo específico, lo que puede llevar a una comparación no estricta que resulte en algo inesperado. Todo se puede cuidar, pero aún se abre para errores inesperados.

¿Es buena o mala práctica encasillar los argumentos recibidos para un método? ¿Y es bueno encasillar la devolución?

IE

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

El ejemplo es bastante estúpida y no he probado, pero creo que todo el mundo tiene la idea. ¿Hay alguna razón para que PHP-god convierta el tipo de datos como quiera, además de permitir que las personas que no conocen los tipos de datos usen PHP?

+1

Puede pensar que la tipificación débil (como en PHP, javascript, Smalltalk, c, C++, etc.) es un defecto, pero es parte del diseño de PHP, y por lo tanto es poco probable que cambie. También es una de las características que hace que PHP sea un lenguaje de tipo c. – GZipp

+2

@GZopp: C y C++ no son idiomas débilmente tipados. –

+0

En realidad, puede requerir que los argumentos sean de cierto tipo, pero solo funciona con clases y matrices, no con otros tipos incorporados. http://www.php.net/manual/en/functions.arguments.php#98425 – mwhite

Respuesta

20

Para mejor o peor, suelta-escribir es "The Way PHP". Muchos de los integradores, y la mayoría de los constructos de lenguaje, operarán en los tipos que les proporciones: silenciosamente (y con frecuencia peligrosamente) lanzándolos detrás de las escenas para hacer que las cosas (más o menos) encajen juntas.

Procedente de un fondo Java/C/C++, el modelo de mecanografía de PHP siempre ha sido una fuente de frustración para mí. Pero a través de los años he descubierto que, si tengo que escribir PHP, puedo hacer un mejor trabajo (es decir, un código más limpio, más seguro y más comprobable) adoptando la "soltura" de PHP, en lugar de luchar contra ella; y termino siendo un mono más feliz por eso.

Casting realmente es fundamental para mi técnica, y (en mi humilde opinión) es la única forma de construir consistentemente un código PHP limpio y legible que maneje argumentos mixtos de una manera bien entendida, comprobable y determinista.

El punto principal (que usted también comprende claramente) es que, en PHP, no puede simplemente asumir que un argumento es del tipo que espera que sea. Si lo hace, puede tener consecuencias graves que probablemente no detectará hasta después de que su aplicación haya comenzado a producirse.

Para ilustrar este 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 enfoque para la solución de este es para restringir los tipos que la función puede aceptar, lanzar una excepción cuando se detecta un tipo no válido. Otros ya han mencionado este enfoque. Apela bien a mi estética Java/C/C++, y seguí este enfoque en PHP durante años y años. En resumen, no hay nada de malo en ello, pero va en contra de "The PHP Way", y después de un tiempo, eso comienza a parecerse a la corriente ascendente.

Como alternativa, la fundición proporciona una manera simple y limpia de garantizar que la función se comporte de manera determinista para todas las entradas posibles, sin tener que escribir una lógica específica para manejar cada tipo diferente.

El uso de fundición, nuestro ejemplo ahora se convierte en:

<?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 función se comporta ahora como se esperaba. De hecho, es fácil mostrar que el comportamiento de la función ahora está bien definido para todas las entradas posibles. Esto se debe a que la operación de lanzamiento está bien definida para todas las entradas posibles; los lanzamientos aseguran que siempre estamos trabajando con enteros; y el resto de la función está escrita para estar bien definida para todos los enteros posibles.

Rules for type-casting in PHP are documented here, (consulte los enlaces específicos del tipo en la mitad de la página, p. Ej .: "Convertir a entero").

Este enfoque tiene la ventaja añadida de que la función ahora se comportará de manera coherente con otras construcciones de lenguaje PHP incorporadas. Por ejemplo:

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

funcionará bien, a pesar del hecho de que $db_row['boys'] y $db_row['girls'] son en realidad las cadenas que contienen valores numéricos. Esto es consistente con la forma en que el desarrollador promedio de PHP (que no conoce C, C++ o Java) esperará que funcione.


En cuanto a la fundición valores de retorno: hay muy poco sentido hacerlo, a menos que usted sabe que tiene una variable potencialmente de tipo mixto, y que desea asegurarse siempre de que el valor de retorno es un tipo específico. Este es más frecuentemente el caso en puntos intermedios en el código, en lugar de en el punto en el que regresas de una función.

Un ejemplo práctico:

<?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"); 

se entiende la idea.


Hay un par de pifias a tener en cuenta el mecanismo de fundición

  • de PHP no es lo suficientemente inteligente como para optimizar la "no-op" fundido. Entonces, fundir siempre provoca que se realice una copia de la variable. En la mayoría de los casos, esto no es un problema, pero si usa este enfoque regularmente, debe tenerlo en cuenta. Debido a esto, la conversión puede causar problemas inesperados con referencias y arreglos grandes. Ver PHP Bug Report #50894 para más detalles.

  • En php, un número entero que es demasiado grande (o demasiado pequeño) para representarlo como un tipo entero, se representará automáticamente como un flotante (o un doble, si es necesario). Esto significa que el resultado de ($big_int + $big_int) puede ser realmente un flotador, y si lo lanzas a un int, el número resultante será un galimatías. Por lo tanto, si está creando funciones que necesitan operar en números enteros grandes, debe tener esto en cuenta, y probablemente considere otro enfoque.


Lo siento por el largo post, pero es un tema que he considerado en profundidad, ya través de los años, he acumulado un poco de conocimiento (y de opinión) sobre él. Al publicarlo aquí, espero que alguien lo encuentre útil.

+1

Me pareció realmente útil, habría subido varias veces si fuera posible. – cantera

0

No, no es bueno encasillar porque no sabes lo que tendrás al final. Yo personalmente sugerir el uso de funciones tales como intval(), floatval(), etc.

+1

¿Cómo es eso? O bien el elenco tendrá éxito y usted tendrá el valor de tipo correcto, o la ejecución fallará (se lanzará una excepción o se bloqueará PHP, dependiendo de la versión de PHP). ¿Hay algo que me falta aquí? –

+0

También tengo curiosidad por lo que dice Billy. intval() hace lo mismo que el tipo de fundición con (int) – Anders

2

Puede utilizar type hinting para este tipo de complejos. Si necesita comparar el valor + tipo, puede usar "===" para comparar.

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

También escribe return (array)$result; que no tiene sentido. Lo que desea en este caso es return array($result) si desea que el tipo de devolución sea una matriz.

+0

Soy consciente de los defectos en el ejemplo. Es la manera en que lo haría en C, pero como dices, definitivamente no lo haría en PHP. Fue principalmente por la coherencia en el ejemplo. Soy consciente de la sugerencia de tipos, pero no funciona para los tipos primitivos :(Solo buscará la clase "int" en lugar de – Anders

2

No creo que sea malo, pero iría un paso más allá: use type hinting para tipos complejos y haga una excepción si un tipo simple no es el que espera. De esta forma, hará que los clientes conozcan cualquier costo/problema con el elenco (como la pérdida de precisión desde int -> float o float -> int).

No obstante, su conversión a una matriz en el código anterior es engañosa; solo debe crear una nueva matriz que contenga el único valor.

Dicho todo esto, el ejemplo anterior se convierte en:

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

Creo que esta es una gran mejora para mis pensamientos. ¿Hay algún problema por el que preocuparse? – Anders

+2

Sí, llamar a una función en PHP es lento. Pero como la optimización prematura es la raíz de todo mal, no pienses en esta desaceleración menor y sin importancia, pero piensa en la legibilidad y mantenimiento de tu código;) – NikiC

+0

@Anders: Sí, hay está sobrecargado, pero no creo que sea significativo aquí. PHP tiene que verificar el tipo para ejecutar el cast en cualquier caso. Por supuesto, el cheque no es gratuito. –

4

La próxima versión de PHP (probablemente 5.4) will support scalar type hinting in arguments.

Pero aparte de eso: la conversión de tipo dinámico realmente no es algo que debas odiar y evitar. En general, funcionará como se espera. Y si no lo hace, fijarla mediante la comprobación de que is_* de algún tipo, mediante el uso de comparación estricta, ..., ...

+2

No estoy seguro de que eso haya sido acordado (otra vez, en php.internamente solo se necesitan 2 personas hablando en privado para concertar una publicación). Si bien hay un parche para eso, no estoy seguro si se acordó que sería en PHP. – Ross

+0

Err .. php ya admite sugerencias tipo (y tiene por un tiempo). –

+0

@Billy: No es compatible con los consejos de tipo escalar y de eso es de lo que estamos hablando aquí. – NikiC

Cuestiones relacionadas