web-dev-qa-db-fra.com

Tapez les variables de casting en PHP, quelle est la raison pratique de le faire?

PHP, comme la plupart d'entre nous le savent, a typage faible . Pour ceux qui ne le font pas, PHP.net dit:

PHP ne nécessite pas (ou ne prend pas 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.

Aimez-le ou détestez-le, PHP re-convertit 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 la vie de moi, je ne peux pas concevoir de raison pratique pour faire ça.

Je n'ai pas de problème avec une frappe forte dans les langages qui le supportent, comme Java. C'est bien, et je le comprends parfaitement. De plus, je connais - et je comprends parfaitement l'utilité de - type hinting dans les paramètres de fonction.

Le problème que j'ai avec la conversion de type est expliqué par la citation ci-dessus. Si PHP peut permuter les types à volonté , il peut le faire même après vous forcez le cast un type; et il peut le faire à la volée lorsque vous avez besoin d'un certain type dans une opération. Cela rend les éléments suivants valides:

$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 type définie par l'utilisateur a du sens en PHP :

  1. Vous forcez la variable cast $foo comme int(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. Soudain, la raison de la conversion de type définie par l'utilisateur existe!

Le fait que PHP permutera les choses selon les besoins rend le point du type défini par l'utilisateur imprécis. 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 transtypage dans un environnement pratique? Votre sincèrement.

L'exigence était d'afficher les valeurs monétaires sur un site Web pour un menu de restaurant. La conception du site exigeait que les zéros de fin soient coupés, de sorte que l'affichage ressemble à ceci:

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 flottant:

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

PHP coupe les zéros de fin du flottant, puis refond le flottant sous forme de chaîne pour la concaténation.

45
Stephen

Dans un langage faiblement typé, la conversion de type existe pour lever toute ambiguïté dans les opérations typées, sinon le compilateur/interprète utiliserait l'ordre ou d'autres règles pour faire l'hypothèse de l'opération à utiliser.

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

Voici ces cas, en utilisant JavaScript comme langage de comparaison.

Concatentation de chaînes

Évidemment, ce n'est pas un problème dans PHP car il y a une concaténation de chaînes séparée (.) et l'addition (+) les opérateurs.

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

Comparaison de chaînes

Souvent, dans les systèmes informatiques, "5" est supérieur à "10" car il ne l'interprète pas comme un nombre. Ce n'est pas le cas en PHP, qui, même si les deux sont des chaînes, se rend compte que ce sont des nombres et supprime le besoin d'un cast):

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

Saisie de signature de fonction

PHP implémente une vérification de type à nu sur les signatures de fonction, mais malheureusement, il est si imparfait qu'il est probablement rarement utilisable.

Je pensais que je faisais quelque chose de mal, mais un commentaire sur les documents confirme que les types intégrés autres que tableau ne peuvent pas être utilisés dans PHP signatures de fonction - bien que le le message d'erreur est 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 transmis à 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 par rapport à 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 peux penser est ... (roulement de tambour)

Type de troncature

En d'autres termes, lorsque vous avez une valeur d'un type (disons une chaîne) et que vous souhaitez l'interpréter comme un autre type (int) et que vous souhaitez la 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 (vers un type qui englobe plus de valeurs) vous gagnerait vraiment.

Donc, bien que je ne sois pas d'accord avec l'utilisation que vous proposez de taper (vous proposez essentiellement typage statique , mais avec l'ambiguïté que si c'était force-cast dans un type, cela jette une erreur - ce qui provoquerait de la confusion), je pense que c'est une bonne question, car apparemment le casting n'a très pas grand-chose en PHP.

32
Nicole

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

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

Un "transtypage de type" est une expression qui produit une nouvelle valeur d'un type différent de l'original; il ne fait rien à la variable (s'il y en a une).

La seule situation où je tape régulièrement des valeurs de conversion concerne les paramètres SQL numériques. Vous êtes censé purifier/échapper toute valeur d'entrée que vous insérez dans des instructions SQL, ou (bien 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 simplement la convertir.

Considérer:

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

si j'ai omis la première ligne, $id serait un vecteur facile pour l'injection SQL. Le casting s'assure qu'il s'agit d'un entier inoffensif; toute tentative d'insertion de code SQL entraînerait simplement une requête pour id=0

15
Javier

Une utilisation pour la conversion de type en PHP que j'ai trouvée:

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

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

Mais, en utilisant PHP type cast _ (int) sur l'ID de l'utilisateur lors du stockage de l'objet PHP, je le renvoie à la place à 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 sur un entier!

Voir, très utile.

4
Ryan

Un exemple est les 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 truc supplémentaire est la ponctuation, qui prend plus de temps à taper. Je pense aussi que c'est plus lisible, bien que d'autres puissent être en désaccord.

Un autre consiste à créer un tableau à un seul élément: array($item); vs (array) $item;. Cela mettra tout type scalaire (entier, ressource, etc.) à l'intérieur d'un tableau.
Alternativement, si $item est un objet, ses propriétés deviendront les clés de 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 documentation PHP : les variables privées ont le nom de classe ajouté au 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 conformes à ANSI pourraient rejeter les données. La raison pour laquelle je ne mentionne que les bases de données est que dans la plupart des autres cas, les données subiront une opération selon leur type à un moment donné (c'est-à-dire que int/floats auront généralement des calculs effectués sur elles, etc.).

3
Alan Pearce

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

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

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

Évidemment, cela est imparfait et également géré implicitement par l'opérateur de comparaison dans l'instruction if, mais il est utile pour vous assurer de savoir exactement ce que fait votre code.

0
Gruffputs

Ce script:

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

fonctionnera bien pour script.php?tags[]=one mais échouera pour script.php?tags=one, parce que _GET['tags'] renvoie un tableau dans le premier cas mais pas dans le second. Étant donné que le script est écrit pour 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 correctement le résultat à partir de _GET:

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