2010-09-12 7 views
12

Après quelques travaux en C et en Java, j'ai été de plus en plus agacé par les lois du Far West en PHP. Ce que je ressens vraiment que PHP manque, ce sont des types de données stricts. Le fait que string ('0') == (int) 0 == (boolean) false est un exemple.PHP Typecasting - Bon ou mauvais?

Vous ne pouvez pas compter sur le type de données renvoyé par une fonction. Vous ne pouvez pas forcer les arguments d'une fonction à être d'un type spécifique, ce qui peut conduire à une comparaison non stricte entraînant quelque chose d'inattendu. Tout peut être pris en charge, mais il reste ouvert pour des bugs inattendus.

Est-ce une bonne ou une mauvaise pratique de typer les arguments reçus pour une méthode? Et est-ce bon de typer le retour?

IE

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

L'exemple est tout à fait stupide et je l'ai pas testé, mais je pense que tout le monde a l'idée. Y a-t-il une raison pour que PHP-dieu convertisse le type de données comme il veut, en plus de laisser les personnes qui ne connaissent pas les types de données utiliser PHP?

+1

Vous pouvez penser que le typage faible (comme en PHP, javascript, Smalltalk, c, C++, etc) est une faille, mais cela fait partie de la conception de PHP, et donc peu susceptible de changer. C'est aussi l'une des caractéristiques qui font de PHP un langage de type c. – GZipp

+2

@GZopp: C et C++ ne sont pas des langages faiblement typés. –

+0

Vous pouvez réellement exiger que les arguments soient d'un certain type, mais cela ne fonctionne qu'avec des classes et des tableaux, pas d'autres types intégrés. http://www.php.net/manual/fr/functions.arguments.php#98425 – mwhite

Répondre

20

Pour le meilleur ou le pire, lâche-frappe est « The Way PHP ». La plupart des constructions intégrées, et la plupart des constructions de langage, fonctionneront sur tous les types que vous leur donnez - silencieusement (et souvent dangereusement) les coulant dans les coulisses pour faire des choses (sorte de) s'emboîtent.

Venant moi-même d'un arrière-plan Java/C/C++, le modèle lâche de PHP a toujours été une source de frustration pour moi. Mais au fil des années, j'ai découvert que si je devais écrire PHP, je pourrais mieux l'utiliser (c'est-à-dire un code plus propre, plus sûr et plus testable) en adoptant le "relâchement" de PHP plutôt que de le combattre. et je finis un singe plus heureux à cause de cela.

Le moulage est vraiment fondamental pour ma technique - et (IMHO) c'est la seule façon de construire de manière cohérente du code PHP propre et lisible qui gère les arguments de type mixte de manière bien compréhensible, testable et déterministe.

Le point principal (que vous comprenez bien aussi) est que, en PHP, vous ne pouvez pas simplement supposer qu'un argument est le type attendu. Cela peut avoir de graves conséquences que vous ne risquez pas d'attendre avant que votre application ne soit mise en production.

Pour illustrer ce point:

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

Une approche pour résoudre consiste à limiter les types que la fonction peut accepter, lancer une exception en cas de détection d'un type non valide. D'autres ont déjà mentionné cette approche. Il fait bon appel à mon esthétique Java/C/C++, et j'ai suivi cette approche en PHP depuis des années et des années. En bref, il n'y a rien de mal à cela, mais cela va à l'encontre de "The PHP Way", et après un certain temps, cela commence à avoir l'impression de nager en amont. En variante, le moulage fournit une manière simple et propre de s'assurer que la fonction se comporte de façon déterministe pour toutes les entrées possibles, sans avoir à écrire une logique spécifique pour gérer chaque type différent.

En utilisant la coulée, notre exemple devient:

<?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 fonction se comporte maintenant comme prévu. En fait, il est facile de montrer que le comportement de la fonction est maintenant bien défini pour tous entrées possibles. C'est parce que l'opération de moulage est bien définie pour toutes les entrées possibles; les moulages s'assurent que nous travaillons toujours avec des nombres entiers; et le reste de la fonction est écrit de manière à être bien défini pour tous les entiers possibles.

Rules for type-casting in PHP are documented here, (voir les liens spécifiques au type au milieu de la page - par exemple: "Conversion en nombre entier").

Cette approche présente l'avantage supplémentaire que la fonction se comporte désormais de manière cohérente avec les autres composants PHP et les constructions de langage. Par exemple:

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

fonctionnera très bien, malgré le fait que $db_row['boys'] et $db_row['girls'] sont en fait des chaînes qui contiennent des valeurs numériques. Ceci est cohérent avec la façon dont le développeur PHP moyen (qui ne connait pas C, C++ ou Java) s'attend à ce qu'il fonctionne.


En ce qui concerne la coulée des valeurs de retour: il y a très peu de point à le faire, à moins que vous savez que vous avez une variable de type mixte potentiellement, et que vous voulez toujours veiller à ce que la valeur de retour est un type spécifique. C'est plus souvent le cas aux points intermédiaires du code, plutôt qu'au point où vous revenez d'une fonction.

Un exemple pratique:

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

Vous voyez l'idée.


Il y a quelques années Gotcha pour être au courant du mécanisme de coulée de

  • PHP est pas assez intelligent pour optimiser la "no-op" casting. Donc, la conversion provoque toujours une copie de la variable à effectuer. Dans la plupart des cas, ce n'est pas un problème, mais si vous utilisez régulièrement cette approche, vous devriez garder cela à l'esprit. Pour cette raison, la diffusion peut provoquer des problèmes inattendus avec les références et les grands tableaux. Voir PHP Bug Report #50894 pour plus de détails.

  • En PHP, un nombre entier qui est trop grand (ou trop petit) pour représenter un type entier, sera automatiquement représenté comme un flottant (ou un double, si nécessaire). Cela signifie que le résultat de ($big_int + $big_int) peut effectivement être un flottant, et si vous le lancez dans un int, le résultat sera du charabia. Donc, si vous construisez des fonctions qui doivent fonctionner sur de grands nombres entiers, vous devriez garder cela à l'esprit, et probablement envisager une autre approche.


Désolé pour le long courrier, mais il est un sujet que j'ai réfléchi en profondeur, et au fil des ans, j'ai accumulé un peu de connaissances (et d'opinion) à ce sujet. En l'exposant ici, j'espère que quelqu'un le trouvera utile.

+1

Trouvé cela vraiment utile - aurait upvoted plusieurs fois si c'était possible. – cantera

0

Non, ce n'est pas bon de le cataloguer car vous ne savez pas ce que vous aurez à la fin. Je suggère personnellement d'utiliser des fonctions telles que intval(), floatval(), etc.

+1

Comment ça? La distribution réussira et vous aurez la valeur de type correcte, ou l'exécution échouera (une exception sera levée ou PHP sera bombardé, selon la version de PHP). Y a-t-il quelque chose qui me manque ici? –

+0

Je suis aussi curieux de savoir ce que dit Billy. intval() coutures à faire exactement la même chose que le type de casting avec (int) – Anders

2

Vous pouvez utiliser type hinting pour les types complexes. Si vous avez besoin de comparer la valeur + le type, vous pouvez utiliser "===" pour la comparaison.

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

vous écrivez également return (array)$result; qui n'a aucun sens. Ce que vous voulez dans ce cas est return array($result) si vous voulez que le type de retour soit un tableau.

+0

Je suis conscient des failles dans l'exemple. C'est comme ça que je le ferais en C, mais comme tu le dis je ne le ferais certainement pas en PHP. C'était surtout pour la cohérence dans l'exemple. Je suis conscient de l'indice de type, mais cela ne fonctionne pas pour les types primitifs :(Il va juste chercher la classe "int" à la place – Anders

2

Je ne pense pas que ce soit mauvais, mais je voudrais aller un peu plus loin: Utilisez type hinting pour les types complexes, et lancez une exception si un type simple n'est pas celui que vous attendez. De cette façon, vous sensibilisez les clients aux coûts/problèmes liés à la distribution (tels que la perte de précision due à int -> float ou float -> int).

Votre conversion en tableau dans le code ci-dessus est cependant trompeuse - vous devriez simplement créer un nouveau tableau contenant la valeur unique.

Que tous dit, votre exemple ci-dessus devient:

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

Je pense que c'est une grande amélioration à mes pensées. – Anders

+2

Ouais, appeler une fonction en PHP est lent, mais comme l'optimisation prématurée est la racine de tout mal, ne pensez pas à ce ralentissement minime et sans importance, mais pensez à la lisibilité et à la maintenabilité de votre code;) – NikiC

+0

@Anders: Oui, là est frais généraux, mais je ne pense pas que cela soit significatif ici. PHP doit vérifier le type pour effectuer le cast dans tous les cas. Bien sûr, le chèque n'est pas gratuit. –

4

La prochaine version de PHP (probablement 5.4) will support scalar type hinting in arguments. Mais en dehors de cela: la conversion de type dynamique n'est vraiment pas quelque chose que vous devriez détester et éviter. Généralement, cela fonctionnera comme prévu. Et si elle ne le fait pas, le corriger en cochant la case is_* d'un certain type, en utilisant la comparaison stricte, ..., ...

+2

Je ne suis pas sûr si cela a été convenu (encore une fois, sur PHP.internes il ne faut que 2 personnes en privé pour organiser une sortie). Bien qu'il y ait un patch pour cela, je ne suis pas sûr qu'il ait été convenu que ce serait en PHP. – Ross

+0

Err .. php supporte déjà l'indication de type (et a pour un moment). –

+0

@Billy: Il ne supportait pas les indications de type scalaire et c'est de cela que nous parlons ici. – NikiC

Questions connexes