Escriba variables de conversión en PHP, ¿cuál es la razón práctica para hacer esto?

45

PHP, como la mayoría de nosotros sabemos, tiene una escritura débil . Para aquellos que no, PHP.net dice:

PHP no requiere (o admite) la definición de tipo explícito en la declaración de variable; El tipo de una variable está determinado por el contexto en el que se utiliza la variable.

Me encanta o lo odio, PHP vuelve a emitir variables sobre la marcha. Entonces, el siguiente código es válido:

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

PHP también le permite emitir explícitamente una variable, así:

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

Eso es genial ... pero, por mi vida, no puedo concebir una razón práctica para hacer esto.

No tengo ningún problema con la escritura fuerte en idiomas que lo admiten, como Java. Eso está bien, y lo entiendo completamente. Además, soy consciente de, y entiendo completamente la utilidad de, las sugerencias de tipo en los parámetros de la función.

El problema que tengo con la conversión de tipos se explica en la cita anterior. Si PHP puede intercambiar tipos a voluntad , puede hacerlo incluso después de forzar la conversión de un tipo; y puede hacerlo sobre la marcha cuando necesite un determinado tipo en una operación. Eso hace que lo siguiente sea válido:

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

¿Entonces cuál es el punto?


Tome este ejemplo teórico de un mundo donde la conversión de tipos definida por el usuario tiene sentido en PHP :

  1. Fuerza la variable de lanzamiento $foocomo int(int)$foo.
  2. Intenta almacenar un valor de cadena en la variable $foo.
  3. PHP lanza una excepción !! ← Eso tendría sentido. De repente, existe la razón para la conversión de tipos definida por el usuario.

El hecho de que PHP cambie las cosas según sea necesario hace que el punto de conversión de tipo definido por el usuario sea vago. Por ejemplo, los siguientes dos ejemplos de código son equivalentes:

// 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 año después de hacer esta pregunta originalmente, ¿adivina quién se encontró usando la conversión de texto en un entorno práctico? Atentamente.

El requisito era mostrar valores monetarios en un sitio web para un menú de restaurante. El diseño del sitio requería que se recortaran los ceros finales, de modo que la pantalla se viera como la siguiente:

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

La mejor manera que encontré para hacerlo fue lanzar la variable como flotante:

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

PHP recorta los ceros finales del flotador y luego vuelve a refundir el flotador como una cadena para la concatenación.

Stephen
fuente
Esto -> $ valor = $ valor. 'TaDa!'; Lanzaría $ value nuevamente a la cadena antes de hacer la asignación al valor final de $ value. No es realmente una sorpresa que si fuerzas un tipo de molde obtienes un tipo de molde. ¿No estás seguro de cuál es el punto de preguntar cuál es el punto?
Chris
"# 3. ¡PHP lanza una excepción! <--- Eso tendría sentido". En realidad, eso no tendría ningún sentido. Eso ni siquiera es un problema en Java, JavaScript o cualquier otro lenguaje de sintaxis C que yo sepa. ¿Quién en su sano juicio vería eso como un comportamiento deseable? ¿Quieres tener (string)moldes por todas partes ?
Nicole
@Renesis: me malinterpretas. Lo que quise decir es que solo se generaría una excepción si un usuario ha convertido una variable en una tipografía. El comportamiento normal (donde PHP hace el casting por ti), por supuesto, no arrojaría una excepción. Estoy tratando de decir que la conversión de tipos definida por el usuario es discutible , pero si se lanzara una excepción, de repente tendría sentido.
Stephen
Si estás diciendo $intval.'bar'lanza una excepción, todavía estoy en desacuerdo. Eso no arroja una excepción en ningún idioma. (Todos los idiomas que conozco realizan una transmisión automática o una .toString()). Si está diciendo $intval = $stringvalarroja una excepción, entonces está hablando de un lenguaje fuertemente tipado. No quise sonar grosero, así que lo siento si lo hice. Simplemente creo que va en contra de lo que todos los desarrolladores están acostumbrados, y es mucho, mucho menos conveniente.
Nicole
@Stephen: publiqué una respuesta después de una investigación. Resultados realmente interesantes: pensé que 2 de los casos seguramente mostrarían un propósito para la conversión, pero PHP es aún más extraño de lo que pensaba.
Nicole

Respuestas:

32

En un lenguaje de tipo débil, la conversión de tipos existe para eliminar la ambigüedad en las operaciones de tipo, cuando de lo contrario el compilador / intérprete usaría el orden u otras reglas para suponer qué operación usar.

Normalmente diría que PHP sigue este patrón, pero de los casos que he verificado, PHP se ha comportado de manera contra intuitiva en cada uno.

Estos son esos casos, usando JavaScript como lenguaje de comparación.

Concatenación de cadenas

Obviamente, esto no es un problema en PHP porque hay operadores de concatenación de cadenas ( .) y suma ( +) separados .

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

Comparación de cadenas

A menudo, en los sistemas informáticos, "5" es mayor que "10" porque no lo interpreta como un número. No es así en PHP, que, incluso si ambas son cadenas, se da cuenta de que son números y elimina la necesidad de un reparto):

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

Mecanografía de firma de función

PHP implementa una verificación de tipos básica en las firmas de funciones, pero desafortunadamente es tan defectuosa que probablemente rara vez se pueda usar.

Pensé que podría estar haciendo algo mal, pero un comentario en los documentos confirma que los tipos integrados distintos de la matriz no se pueden usar en las firmas de funciones de PHP, aunque el mensaje de error es engañoso.

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?

Y a diferencia de cualquier otro idioma que conozco, incluso si usa un tipo que entiende, nulo ya no se puede pasar a ese argumento ( must be an instance of array, null given). Que estúpido.

Interpretación booleana

[ Editar ]: Este es nuevo. Pensé en otro caso, y nuevamente la lógica se invierte de 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.

En conclusión, el único caso útil que se me ocurre es ... (redoble de batería)

Tipo de truncamiento

En otras palabras, cuando tiene un valor de un tipo (digamos cadena) y quiere interpretarlo como otro tipo (int) y quiere forzarlo a convertirse en uno de los conjuntos de valores válidos en ese 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

No puedo ver qué upcasting (para un tipo que abarca más valores) realmente te ganaría.

Entonces, aunque no estoy de acuerdo con su uso propuesto de mecanografía (esencialmente está proponiendo mecanografía estática , pero con la ambigüedad de que solo si se forzara en un tipo arrojaría un error, lo que causaría confusión), creo que es un buen pregunta, porque aparentemente el casting tiene muy poco propósito en PHP.

Nicole
fuente
Bien, ¿qué tal un E_NOTICEentonces? :)
Stephen
@Stephen E_NOTICEpodría estar bien, pero para mí el estado ambiguo es preocupante: ¿cómo podría saber mirando un bit de código si la variable estaba en ese estado (después de haber sido emitida en otro lugar)? Además, encontré otra condición y la agregué a mi respuesta.
Nicole
1
En cuanto a la evaluación booleana, los documentos PHP establecen claramente lo que se considera falso cuando se evalúa como booleano, y tanto la cadena vacía como la cadena "0" se consideran falsas. Entonces, incluso cuando esto se siente extraño, es un comportamiento normal y esperado.
Jacek Prucia
para agregar un poco a la confusión: echo "010" == 010 y echo "0x10" == 0x10;-)
vartec
1
Tenga en cuenta que a partir de PHP 7 , las notas de esta respuesta sobre sugerencias de tipo escalar son inexactas.
John V.
15

Estás mezclando los conceptos de tipo débil / fuerte y dinámico / estático.

PHP es débil y dinámico, pero su problema es con el concepto de tipo dinámico. Eso significa que las variables no tienen un tipo, los valores sí.

Una 'conversión de tipo' es una expresión que produce un nuevo valor de un tipo diferente del original; no hace nada a la variable (si hay una involucrada).

La única situación en la que escribo regularmente valores de conversión es en parámetros numéricos de SQL. Se supone que debe desinfectar / escapar cualquier valor de entrada que inserte en las instrucciones SQL, o (mucho mejor) usar consultas parametrizadas. Pero, si desea un valor que DEBE ser un número entero, es mucho más fácil simplemente lanzarlo.

Considerar:

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

Si omitiera la primera línea, $idsería un vector fácil para la inyección de SQL. El elenco se asegura de que sea un entero inofensivo; cualquier intento de insertar algo de SQL simplemente resultaría en una consulta paraid=0

Javier
fuente
Acepto eso. Ahora, en cuanto a la utilidad de Type Casting?
Stephen
Es curioso que aparezca la inyección SQL. Estaba discutiendo sobre SO con alguien que usa esta técnica para desinfectar la entrada del usuario. Pero, ¿qué problema resuelve este método que mysql_real_escape_string($id);ya no lo hace?
Stephen
es más corto :-) por supuesto, para cadenas utilizo consultas parametrizadas, o (si utilizo la antigua extensión mysql) escapar de ella.
Javier
2
mysql_real_escape_string()tiene la vulnerabilidad de no hacer nada a cadenas como '0x01ABCDEF' (es decir, representación hexadecimal de un entero). En algunas codificaciones multibyte (no unicode por suerte), una cadena como esta se puede utilizar para romper la consulta (porque MySQL la evalúa como algo que contiene una cita). Es por eso que ni mysql_real_escape_string()tampoco is_int()es la mejor opción para tratar con valores enteros. La conversión de texto es.
Mchl
Un enlace con algunos detalles más: ilia.ws/archives/…
Mchl
4

Un uso para la conversión de tipos en PHP que he encontrado:

estoy desarrollando una aplicación de Android que realiza solicitudes http a scripts PHP en un servidor para recuperar datos de una base de datos. El script almacena datos en forma de un objeto PHP (o matriz asociativa) y se devuelve como un objeto JSON a la aplicación. Sin el tipo de casting, recibiría algo como esto:

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

Pero, al usar la conversión de tipo PHP (int)en la identificación del usuario al almacenarlo en el objeto PHP, en su lugar obtengo esto devuelto a la aplicación:

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

Luego, cuando el objeto JSON se analiza en la aplicación, me ahorra tener que analizar la identificación a un número entero.

Mira, muy útil.

Ryan
fuente
No había considerado formatear los datos para que los sistemas externos de tipo fuerte los consumieran. +1
Stephen
Esto es especialmente cierto cuando se habla de JSON a sistemas externos como Elasticsearch. Un valor de json_encode () - ed "5" dará resultados muy diferentes que el valor 5.
Johan Fredrik Varen
3

Un ejemplo es objetos con un método __toString: $str = $obj->__toString();vs $str = (string) $obj;. Hay mucho menos tipeo en el segundo, y el material adicional es la puntuación, que lleva más tiempo escribir. También creo que es más legible, aunque otros pueden estar en desacuerdo.

Otra es hacer una matriz de un solo elemento: array($item);vs (array) $item;. Esto colocará cualquier tipo escalar (entero, recurso, etc.) dentro de una matriz.
Alternativamente, si $itemes un objeto, sus propiedades se convertirán en claves para sus valores. Sin embargo, creo que la conversión de objeto-> matriz es un poco extraña: las propiedades privadas y protegidas son parte de la matriz y se renombraron. Para citar la documentación de PHP : las variables privadas tienen el nombre de la clase antepuesto al nombre de la variable; Las variables protegidas tienen un '*' antepuesto al nombre de la variable.

Otro uso es convertir datos GET / POST en tipos apropiados para una base de datos. MySQL puede manejar esto por sí mismo, pero creo que los servidores más compatibles con ANSI podrían rechazar los datos. La razón por la que solo menciono las bases de datos es que en la mayoría de los otros casos, los datos tendrán una operación realizada de acuerdo con su tipo en algún momento (es decir, int / floats generalmente tendrán cálculos realizados en ellos, etc.).

Alan Pearce
fuente
Estos son excelentes ejemplos de cómo funciona la conversión de tipos. Sin embargo, no estoy convencido de que satisfagan una necesidad . Sí, puede convertir un objeto en una matriz, pero ¿por qué? Supongo que podría usar las innumerables funciones de matriz PHP en la nueva matriz, pero no puedo entender cómo sería útil. Además, PHP generalmente crea consultas de cadena para enviar a una base de datos MySQL, por lo que el tipo de variable es irrelevante (la conversión automática de cadena into floatse producirá al generar la consulta). (array) $itemes ordenado pero útil?
Stephen
De hecho estoy de acuerdo. Mientras los escribía, pensé que pensaría en algunos usos, pero no lo hice. Para las cosas de la base de datos, si los parámetros son parte de la cadena de consulta, tiene razón, la conversión no tiene ningún propósito. Sin embargo, cuando se utilizan consultas parametrizadas (que siempre es una buena idea), es posible especificar los tipos de parámetros.
Alan Pearce
¡Ajá! Es posible que haya encontrado un motivo válido con consultas parametrizadas.
Stephen
0

Este guión:

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

funcionará bien script.php?tags[]=onepero fallará script.php?tags=oneporque _GET['tags']devuelve una matriz en el primer caso pero no en el segundo. Dado que el script está escrito para esperar una matriz (y usted tiene menos control sobre la cadena de consulta enviada al script), el problema se puede resolver al emitir adecuadamente el resultado de _GET:

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

También se puede usar como un método rápido y sucio para garantizar que los datos no confiables no rompan algo, por ejemplo, si se utiliza un servicio remoto que tiene validación de basura y solo debe aceptar números.

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

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

Obviamente, esto es defectuoso y también lo maneja implícitamente el operador de comparación en la instrucción if, pero es útil para garantizar que sepa exactamente lo que está haciendo su código.

Gruffputs
fuente
1
Excepto que no se rompe. Incluso si $_POST['amount']contuviera una cadena de basura, php evaluaría que no fuera mayor que cero. Si contuviera una cadena que representara un número positivo, evaluaría verdadero.
Stephen
1
No del todo cierto. Considere que la cantidad de $ se transfiere a un servicio de terceros dentro del condicional que debe recibir un número. Si alguien pasara $ _POST ['cantidad'] = "100 bobinas", eliminar (flotante) aún permitiría que el condicional pasara pero $ cantidad no sería un número.
Gruffputs
-2

Un "uso" de PHP que vuelve a emitir variables sobre la marcha que veo en uso a menudo es cuando se recuperan datos de fuentes externas (entrada del usuario o base de datos). Permite a los codificadores (tenga en cuenta que no dije desarrolladores) ignorar (o incluso no aprender) los diferentes tipos de datos disponibles de diferentes fuentes.

Un codificador (tenga en cuenta que no dije desarrollador) cuyo código heredé y aún mantengo no parece saber que existe una diferencia entre la cadena "20"que se devuelve en la $_GETsúper variable, entre la operación entera20 + 20 cuando la agrega a El valor en la base de datos. Ella solo tiene suerte de que PHP use .para la concatenación de cadenas y no +como cualquier otro lenguaje, porque he visto su código "agregar" dos cadenas (una varcahrde MySQL y un valor de $_GET) y obtener un int.

¿Es este un ejemplo práctico? Solo en el sentido de que permite a los codificadores escapar sin saber con qué tipos de datos están trabajando. Yo personalmente lo odio.

dotancohen
fuente
2
No veo cómo esta respuesta agrega valor a la discusión. El hecho de que PHP permite a un ingeniero (o programador, o codificador, lo que tiene) realizar operaciones matemáticas en cadenas ya está muy claro en la pregunta.
Stephen
Gracias Stephen Quizás usé demasiadas palabras para decir "PHP permite a las personas que no saben qué es un tipo de datos crear aplicaciones que hagan lo que esperan en condiciones ideales".
dotancohen