Tapez les variables de conversion en PHP, quelle est la raison pratique pour cela?

45

Comme la plupart d’entre nous le savent, PHP est peu typé . Pour ceux qui ne le font pas, PHP.net dit:

PHP ne nécessite pas (ni ne prend en charge) la définition de type explicite dans la déclaration de variable; le type d'une variable est déterminé par le contexte dans lequel la variable est utilisée.

Qu'on l'aime ou qu'on le déteste, PHP redéfinit les variables à la volée. Donc, le code suivant est valide:

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

PHP vous permet également de convertir explicitement une variable, comme ceci:

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

C'est tout cool ... mais, pour ma vie, je ne peux pas concevoir une raison pratique pour cela.

Le typage fort ne me pose pas de problème dans les langages qui le prennent en charge, comme Java. C'est bien et je le comprends tout à fait. De plus, je suis conscient de l'utilité de l'indication de type dans les paramètres de fonction et comprend parfaitement son utilité .

Le problème que j'ai avec le casting de type est expliqué par la citation ci-dessus. Si PHP peut échanger des types à volonté , il peut le faire même après avoir forcé la conversion d' un type. et il peut le faire à la volée lorsque vous avez besoin d'un certain type d'opération. Cela rend valide ce qui suit:

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

Alors à quoi ça sert?


Prenons cet exemple théorique d'un monde où la conversion de types définie par l'utilisateur a un sens en PHP :

  1. Vous forcez la variable $foode intconversion comme → (int)$foo.
  2. Vous essayez de stocker une valeur de chaîne dans la variable $foo.
  3. PHP lève une exception !! ← Cela aurait du sens. Tout à coup, la raison du transtypage défini par l'utilisateur existe!

Le fait que PHP modifie les choses selon les besoins rend vague le sens du type défini par l'utilisateur. Par exemple, les deux exemples de code suivants sont équivalents:

// 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 an après avoir posé cette question à l'origine, devinez qui s'est retrouvé à utiliser la conversion de texte dans un environnement pratique? Votre sincèrement.

L'exigence était d'afficher des valeurs monétaires sur un site Web pour un menu de restaurant. La conception du site nécessitait que les zéros de fin soient supprimés, de sorte que l'affichage ressemble à peu près à ce qui suit:

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

La meilleure façon que j'ai trouvée de le faire était de convertir la variable en float:

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

PHP élimine les zéros de fin du flottant, puis le remodèle en tant que chaîne pour la concaténation.

Stephen
la source
Ceci -> $ valeur = $ valeur. 'TaDa!'; Lirait $ valeur en chaîne avant de faire l'affectation à la valeur finale de $ valeur. Pas vraiment surprenant que si vous forcez un transtypage, vous en obtenez un. Vous ne savez pas à quoi ça sert de demander quel est le but de cela?
Chris
"# 3. PHP lève une exception !! <--- Cela aurait du sens." En fait, cela n'aurait aucun sens. Ce n'est même pas un problème en Java, JavaScript ou tout autre langage C-syntaxe que je connaisse. Qui, dans leur esprit, verrait cela comme un comportement souhaitable? Voulez-vous avoir des (string)moulages partout ?
Nicole
@Renesis: vous me comprenez mal. Ce que je voulais dire, c'est qu'une exception ne serait levée que si un utilisateur a transtypé une variable. Le comportement normal (où PHP est le casting pour vous) ne créerait bien sûr pas une exception. J'essaie de dire que le casting de type défini par l'utilisateur est sans objet , mais si une exception devait être levée, cela aurait alors tout son sens.
Stephen
Si vous dites $intval.'bar'jette une exception, je suis toujours en désaccord. Cela ne jette une exception dans aucune langue. (Toutes les langues que je connais effectuent une distribution automatique ou a .toString()). Si vous dites $intval = $stringvallève une exception, vous parlez d'un langage fortement typé. Je ne voulais pas avoir l'air impoli, alors, désolé si je l'avais fait. Je pense simplement que cela va à l’encontre de ce que chaque développeur est habitué et est beaucoup, beaucoup moins pratique.
Nicole
@Stephen - J'ai posté une réponse après une enquête. Des résultats vraiment intéressants - je pensais que 2 des cas montreraient à coup sûr une raison d'être, mais PHP est encore plus étrange que je ne le pensais.
Nicole

Réponses:

32

Dans un langage faiblement typé, le transtypage existe pour supprimer toute ambiguïté dans les opérations typées. Dans le cas contraire, le compilateur / interprète utiliserait un ordre ou d’autres règles pour supposer quelle opération utiliser.

Normalement, je dirais que PHP suit ce modèle, mais dans les cas que j'ai vérifiés, PHP s'est comporté de manière contre-intuitive dans chacun d'eux.

Voici ces cas, utilisant JavaScript comme langage de comparaison.

Concatentation de cordes

Évidemment, cela ne pose pas de problème en PHP car il existe des opérateurs de concaténation de chaînes ( .) et d'addition ( +) distincts .

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

Comparaison de chaîne

Souvent, dans les systèmes informatiques, "5" est supérieur à "10" car il n'est pas interprété comme un nombre. Ce n’est pas le cas en PHP, qui, même s’ils sont tous deux des chaînes, réalise qu’ils sont des nombres et supprime le besoin de faire un casting):

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

Dactylographie de signature de fonction

PHP implémente une vérification de type pure et simple sur les signatures de fonctions, mais malheureusement, il est tellement imparfait qu'il est probablement rarement utilisable.

Je pensais pouvoir faire quelque chose de mal, mais un commentaire sur la documentation confirme que des types intégrés autres que array ne peuvent pas être utilisés dans les signatures de fonctions PHP - bien que le message d'erreur soit trompeur.

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?

Et contrairement à tout autre langage que je connais, même si vous utilisez un type qu’il comprend, null ne peut plus être passé à cet argument ( must be an instance of array, null given). Tellement stupide.

Interprétation booléenne

[ Edit ]: Celui-ci est nouveau. J'ai pensé à un autre cas, et encore une fois, la logique est inversée à partir 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.

Donc, en conclusion, le seul cas utile auquel je puisse penser est ... (drumroll)

Type de troncature

En d'autres termes, lorsque vous avez une valeur d'un type (par exemple, chaîne) et que vous souhaitez l'interpréter comme un autre type (int) et que vous souhaitez le forcer à devenir l'un des ensembles de valeurs valides de ce type:

$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

Je ne vois pas ce que l'upcasting (pour un type qui englobe plus de valeurs) pourrait vraiment vous gagner.

Ainsi, bien que je sois en désaccord avec l’utilisation que vous proposez de dactylographie (vous proposez essentiellement une dactylographie statique , mais avec l’ambiguïté voulant que c’est seulement si elle était moulée de force dans un type que cela jetterait une erreur - ce qui causerait de la confusion), je pense que c’est un bon question, car apparemment le casting a très peu de sens en PHP.

Nicole
la source
Ok, pourquoi pas un E_NOTICEalors? :)
Stephen
@Stephen est E_NOTICEpeut-être acceptable , mais pour moi, l'état ambigu est préoccupant. Comment savoir, en regardant un morceau de code, si la variable était dans cet état (après avoir été exprimée ailleurs)? En outre, j'ai trouvé une autre condition et l'a ajouté à ma réponse.
Nicole
1
En ce qui concerne l'évaluation booléenne, la documentation PHP énonce clairement ce qui est considéré comme faux lors de l'évaluation booléenne et les chaînes vides et la chaîne "0" sont considérées comme fausses. Donc, même lorsque cela vous semble bizarre, il s'agit d'un comportement normal et attendu.
Jacek Prucia
ajouter un peu de confusion: echo "010" == 010 et echo "0x10" == 0x10;-)
vartec
1
Notez que depuis PHP 7 , les notes de cette réponse sur les indications de type scalaire sont inexactes.
John V.
15

Vous mélangez les concepts de type faible / fort et dynamique / statique.

PHP est faible et dynamique, mais votre problème réside dans le concept de type dynamique. Cela signifie que les variables n'ont pas de type, les valeurs en ont.

Un 'typage de type' est une expression qui produit une nouvelle valeur d'un type différent de l'original; cela ne fait rien à la variable (si on est impliqué).

La seule situation dans laquelle je tape régulièrement des valeurs de conversion se trouve dans les paramètres numériques SQL. Vous êtes censé supprimer / échapper toute valeur d'entrée que vous insérez dans des instructions SQL ou (encore mieux) utiliser des requêtes paramétrées. Mais si vous voulez une valeur qui DOIT être un entier, il est beaucoup plus facile de la lancer.

Considérer:

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

si je laissais de côté la première ligne, ce $idserait un vecteur facile pour l'injection SQL. La distribution s'assure que c'est un entier inoffensif; toute tentative d’insertion de SQL aboutirait simplement à une requêteid=0

Javier
la source
Je vais accepter ça. Maintenant, en ce qui concerne l'utilité de la typographie?
Stephen
C'est marrant que vous parliez d'injection SQL. Je discutais sur le SO avec quelqu'un qui utilisait cette technique pour nettoyer les entrées de l'utilisateur. Mais quel problème cette méthode ne résout- mysql_real_escape_string($id);elle pas déjà?
Stephen
c'est plus court :-) bien sûr, pour les chaînes, j'utilise des requêtes paramétrées, ou (si vous utilisez l'ancienne extension mysql), vous échappez.
Javier
2
mysql_real_escape_string()est vulnérable à ne rien faire avec des chaînes telles que '0x01ABCDEF' (c'est-à-dire la représentation hexadécimale d'un entier). Dans certains encodages multi-octets (pas Unicode heureusement), une chaîne comme celle-ci peut être utilisée pour casser la requête (car elle est évaluée par MySQL avec quelque chose qui contient un guillemet). C'est pourquoi ni le meilleur choix mysql_real_escape_string()ni is_int()pour le traitement des valeurs entières. Typecasting est.
Mchl
Un lien avec quelques détails supplémentaires: ilia.ws/archives/…
Mchl le
4

Une utilisation que j'ai trouvée

pour la saisie de texte en PHP: Je développe une application Android qui envoie des requêtes http à des scripts PHP sur un serveur afin de récupérer des données d'une base de données. Le script stocke les données sous la forme d'un objet PHP (ou d'un tableau associatif) et est renvoyé sous forme d'objet JSON à l'application. Sans transtypage, je recevrais quelque chose comme ceci:

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

Mais, en utilisant le transtypage de type PHP (int)sur l'identifiant de l'utilisateur lors du stockage de l'objet PHP, je reçois plutôt ceci retourné à l'application:

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

Ensuite, lorsque l'objet JSON est analysé dans l'application, cela m'évite d'avoir à analyser l'ID d'un entier.

Voir, très utile.

Ryan
la source
Je n'avais pas envisagé de mettre en forme les données destinées à être consommées par des systèmes externes fortement typés. +1
Stephen
Cela est particulièrement vrai lorsque vous parlez de JSON à des systèmes externes tels qu'Elasticsearch. Une valeur "5" donnée par json_encode () donnera des résultats très différents de la valeur 5.
Johan Fredrik Varen
3

Un exemple est des objets avec une méthode __toString: $str = $obj->__toString();vs $str = (string) $obj;. Il y a beaucoup moins de dactylographie dans la seconde, et le plus important est la ponctuation, qui prend plus de temps à taper. Je pense aussi que c'est plus lisible, même si d'autres peuvent ne pas être d'accord.

Un autre fait un tableau à un seul élément: array($item);vs (array) $item;. Cela placera n'importe quel type scalaire (entier, ressource, etc.) dans un tableau.
De manière alternative, si $itemest un objet, ses propriétés deviendront des clés pour leurs valeurs. Cependant, je pense que la conversion objet-> tableau est un peu étrange: les propriétés privées et protégées font partie du tableau et sont renommées. Pour citer la documentation PHP : les variables privées ont le nom de la classe précédé du nom de la variable; Les variables protégées ont un '*' ajouté au nom de la variable.

Une autre utilisation consiste à convertir les données GET / POST en types appropriés pour une base de données. MySQL peut gérer cela lui-même, mais je pense que les serveurs plus compatibles ANSI pourraient rejeter les données. La raison pour laquelle je ne parle que de bases de données est que, dans la plupart des autres cas, les données subissent une opération effectuée en fonction de leur type à un moment donné (c’est-à-dire que les calculs int / floats sont généralement effectués, etc.).

Alan Pearce
la source
Ce sont d'excellents exemples de la façon dont fonctionne le transtypage. Pourtant, je ne suis pas convaincu qu'ils répondent à un besoin . Oui, vous pouvez convertir un objet en tableau, mais pourquoi? Je suppose que parce que vous pourriez alors utiliser la myriade de fonctions de tableau PHP sur le nouveau tableau, mais je ne peux pas comprendre en quoi cela serait utile. De plus, PHP crée généralement des requêtes de chaînes à envoyer à une base de données MySQL. Le type de variable n'a donc aucune importance (la conversion automatique des chaînes à partir de intou floatse produira lors de la construction de la requête). (array) $itemest soigné , mais utile?
Stephen
Je suis d'accord En les tapant, je pensais penser à certaines utilisations, mais je ne l’ai pas fait. Pour ce qui est de la base de données, si les paramètres font partie de la chaîne de requête, vous avez raison, la conversion n'a pas de raison d'être. Cependant, lors de l'utilisation de requêtes paramétrées (ce qui est toujours une bonne idée), il est possible de spécifier les types de paramètres.
Alan Pearce
Aha! Vous avez peut-être trouvé une raison valide avec des requêtes paramétrées.
Stephen
0

Ce script:

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

fonctionnera correctement script.php?tags[]=onemais échouera script.php?tags=onecar _GET['tags']retourne un tableau dans le premier cas mais pas dans le second. Étant donné que le script est écrit pour s’attendre à un tableau (et que vous avez moins de contrôle sur la chaîne de requête envoyée au script), le problème peut être résolu en convertissant le résultat de manière appropriée _GET:

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

Il peut également être utilisé comme une méthode rapide et compliquée pour garantir que des données non fiables ne vont pas casser quelque chose, par exemple si vous utilisez un service distant qui a une validation de la merde et doit accepter uniquement les chiffres.

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

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

Évidemment, cela est erroné et également traité implicitement par l'opérateur de comparaison dans l'instruction if, mais il est utile pour vous permettre de savoir exactement ce que votre code est en train de faire.

Gruffputs
la source
1
Sauf que ça ne casse pas. Même s'il $_POST['amount']contenait une chaîne de caractères, php évaluerait qu'elle n'est pas supérieure à zéro. Si elle contenait une chaîne représentant un nombre positif, elle serait évaluée comme étant vraie.
Stephen le
1
Pas tout à fait vrai. Considérons que $ montant est transféré à un service tiers à l'intérieur du conditionnel qui doit recevoir un numéro. Si quelqu'un devait passer $ _POST ['amount'] = "100 bobines", la suppression de (float) permettrait toujours au conditionnel de passer mais $ amount ne serait pas un nombre.
Gruffputs
-2

L'une des "utilisations" des variables de reconfiguration PHP à la volée que je vois souvent utilisées consiste à récupérer des données à partir de sources externes (entrée utilisateur ou base de données). Cela permet aux codeurs (notez que je n'ai pas dit les développeurs) d' ignorer (ou même de ne pas apprendre) les différents types de données disponibles à partir de différentes sources.

Un codeur (notez que je n’ai pas dit développeur) dont le code dont j’ai hérité et que je maintiens toujours ne semble pas savoir qu’il existe une différence entre la chaîne "20"renvoyée dans la $_GETsuper variable, entre l’opération d’entier20 + 20 quand elle l’ajoute la valeur dans la base de données. Elle n'a que de la chance que PHP utilise .pour la concaténation de chaînes et pas +comme toutes les autres langues, car j'ai vu son code "ajouter" deux chaînes (une varcahrde MySQL et une valeur de $_GET) et obtenir un int.

Est-ce un exemple pratique? Seulement dans le sens où cela laisse les codeurs s'en tirer sans savoir quels types de données ils utilisent. Personnellement, je le déteste.

dotancohen
la source
2
Je ne vois pas en quoi cette réponse ajoute de la valeur à la discussion. Le fait que PHP permette à un ingénieur (ou à un programmeur ou à un codeur de faire ce que vous avez) d'effectuer des opérations mathématiques sur des chaînes est déjà très clair dans la question.
Stephen le
Merci Stephen. J'ai peut-être utilisé trop de mots pour dire "PHP permet aux personnes qui ne savent pas ce qu'est un type de données de créer des applications qui font ce qu'elles attendent dans des conditions idéales".
dotancohen