Servez-vous du typage faible de PHP!

Loué par les débutants, exécré par certains gourous, le typage faible est une spécificité importante de PHP. Il permet d’écrire vite fait un petit bout de code, et de ne plus jamais savoir relire un programme. Il fait rentrer n’importe quelle valeur dans n’importe quelle variable. Il autorise une fonction à retourner une donnée à laquelle on ne se serait pas attendu.

Et si le typage faible était un atout?

Le typage faible est-il une des plus grosses failles de PHP? Pas du tout! Il s’agit sans doute d’une des particularités les plus avant-gardistes du langage. Encore faut-il l’utiliser à bon escient!

Les langages fortement typés, c’est pour les mauvais codeurs!

Oui, je sais, les vrais développeurs, les durs-à-cuire, les blousons-de-cuir qui ne tolèrent que la console de commande (avec une syntaxe colorée pour les plus débridés d’entre eux), ceux-là ne jureront que par un typage fort. Seuls les vrais langages sont fortement typés; seul le typage fort permet de ne pas avoir de bugs; etc.

Mais le fait est qu’avec du typage faible, on gagne en légèreté, en lisibilité. Si on écrit du beau code, le typage faible devient un atout. Et il est fort à parier que si vous avez besoin d’un typage fort, c’est que vous ne savez peut-être pas si bien coder que ça! ;-)

Un grand pouvoir implique de grandes responsabilités

Le typage faible, ce n’est pas mal coder. Au contraire, cela demande davantage de réflexion et de rigueur. Ce n’est pas parce que ma voiture a la capacité d’atteindre les 200 km/h qu’il m’est permis, pour autant, de dépasser les vitesses autorisées. Inversement, ce n’est pas parce que vous utiliserez un langage fortement typé que vous écrirez du beau code. Désolé!

Avec le typage faible, on gagne en souplesse, mais à la condition de bien coder.

Mais le typage, c’est quoi en fait?

Tous les langages, qu’ils soient faiblement ou fortement typés, travaillent avec des types de donnée. Un type n’est qu’une forme de représentation d’une donnée en mémoire. Le type permet de distinguer un "1" d’un "true", qui ont pourtant la même emprunte binaire.

Principes de base

Le typage représente les règles de définition de cette représentation. Il se résume en quelques grands concepts que les langages suivent en tout ou en partie.

  • Déclarer le type de donnée (variable, fonction, paramètres…). Avant même l’assignation, le langage doit connaître le type d’emplacement mémoire à réserver à la donnée. On parle de typage explicite ou implicite selon le respect de cette règle.
  • Respecter le type de donnée lors de l’assignation. C’est ce que l’on appelle la sûreté du typage. Si le langage l’exige, il n’est pas possible d’affecter un type de donnée qui ne respecte pas le typage, sans souffrir d’une erreur de compilation/exécution.
  • Préserver le type de donnée lors des manipulations. Les conversions d’un type vers un autre sont explicites ou implicites selon la tolérance des langages. Les langages permettant la conversion implicite n’empêchent toutefois pas la conversion explicite.

Les langages forts implémentent généralement l’ensemble de ces règles, au contraire des langages permissifs. On le sait, PHP ne nécessite bien sûr pas de déclarer un type. On peut affecter n’importe quelle nouveau type de donnée à une variable contenant déjà une valeur, même typée au préalable. Et les conversions implicites sont nombreuses. Bref, PHP offre un typage faible.

Types de donnée

Je ne vous apprendrai rien non plus, PHP comporte les types de donnée habituels. Les types simples (boolean, string, int, float) sont des valeurs dites scalaires, à l’opposé des types complexes (null, array, object, callable, trait, resource). Les objets sont de type object, mais répondent à leur classe, à leurs classes parentes et aux interfaces qu’ils implémentent.

Type hinting

C’est quoi ce truc?

Le type hinting est la seule fonctionnalité de PHP qui permette un typage explicite. Il s’agit de déclarer le type des paramètres d’une fonction ou d’une méthode. (En PHP, méthode et fonction ont un comportement similaire. Une méthode n’est qu’une fonction au sein d’une classe. Aussi, par facilité de langage, j’englobe souvent les méthodes dans le terme de fonction.)

1
2
3
function foo(array $param){

}

Cette déclaration de type n’est donc pas obligatoire. Mais dès lors qu’elle est présente, elle lancera une erreur si l’on tente d’appeler la fonction avec un mauvais argument.

1
2
foo(123); //Catchable fatal error:
//Argument 1 passed to foo() must be of the type array, integer given

Toutefois, seuls les array, les objets et les callable sont concernés. (Il n’est pas tout à fait juste de dire que seules les données de type non scalaire sont concernées, car les resource et les trait ne sont pas typables non plus. Nous verrons plus tard que null est un cas particulier.)

1
2
3
4
5
6
function bar(integer $param){

}

bar(123); //Catchable fatal error:
//Argument 1 passed to foo() must be an instance of integer, integer given

Le message d’erreur est pour le moins étonnant dans ce cas-ci. En réalité, PHP s’attend à recevoir une instance de la classe integer (et pas un int), qui n’existe bien sûr pas.

EDIT: PHP 7 permettra également le typage des scalaires. Seuls null et resource restent malheureusement sans possibilité de typage.

Catcher l’erreur, une mauvaise idée

EDIT: cette partie n’est plus valable pour PHP 7 qui permet le typage des scalaires.

Une catchable fatal error est donc lancée qui coupe le script. Il s’agit d’une erreur de type E_RECOVERABLE_ERROR.

Catchable signifie que l’erreur est certes importante, mais que, contrairement à une Fatal error de type E_ERROR, elle peut être interceptée par un gestionnaire d’erreur.

1
2
3
4
function errorHandler($errno, $errstr, $errfile, $errline) {
  return E_RECOVERABLE_ERROR === $errno;
}
set_error_handler('errorHandler');
1
bar(123); //aucune erreur n'est lancée

Assurément, c’est quelque-chose à ne surtout pas faire! Si le script lance une erreur, c’est qu’il a une bonne raison… Cacher une erreur est la pire des erreurs.

A l’inverse, on pourrait aussi détourner cette fonctionnalité et imaginer typer les paramètres scalaires. Le typage pourrait donc être plus complet que celui de base.

1
2
3
4
5
function errorHandler($errno, $errstr, $errfile, $errline) {
  return E_RECOVERABLE_ERROR === $errno
     && strpos($errstr, 'integer, integer') !== false;
}
set_error_handler('errorHandler');
1
2
3
4
bar(123); //aucune erreur n'est lancée

bar('123');//Catchable fatal error:
//Argument 1 passed to bar() must be an instance of integer, string given

Mais c’est hacker PHP, et mal coder! Pensez un peu aux performances perdues inutilement…

Le hinting, à lui seul, permet de se passer du typage fort

Une bonne fonction est une fonction courte, dans laquelle les variables temporaires doivent être aussi rares que possible. C’est pourquoi elles peuvent, si la fonction est bien écrite, se résumer en grande partie aux seuls paramètres. Autrement dit, une bonne utilisation du hinting permet de typer les variables essentielles qui seront utilisées dans le code.

Nous reviendrons plus en détail sur ce point dans un prochain article.

Pas de hinting pour les scalaires? Pas grave!

EDIT: cette partie n’est plus valable pour PHP 7 qui permet le typage des scalaires.

Bon ok, c’est dommage… mais je dois vous avouer que, de plus en plus, je me dis que c’est une bonne chose, ou en tout cas pas un handicap.

En effet, grâce aux conversions implicites de PHP, les différents types scalaires sont interchangeables. Utilisez un int à la place d’une string: cela marchera. Un bool à la place d’un float: pas de problème. Vous n’aurez jamais d’erreur d’exécution. Et votre fonction aura toujours son comportement attendu.

1
2
3
//en PHP, on peut tout mélanger:

var_dump(1 * '2' + true); //int(3)

Seuls les array et les objets peuvent engendrer des erreurs si leur type n’est pas respecté. Passez un int dans une boucle foreach, cela ne va pas marcher à merveille. Un bool depuis lequel vous appelez une méthode, vous aurez un problème. C’est pour cette raison (enfin, je suppose) que seuls les array et les objets sont concernés par le hinting.

Que faire des scalaires?

EDIT: cette partie n’est plus valable pour PHP 7 qui permet le typage des scalaires.

Pourquoi se compliquer la vie? Si on appelle votre fonction en passant un mauvais argument scalaire, ne vous en préoccupez pas. Ce n’est pas vos affaires. Par contre, à vous de coder suffisamment bien pour ne pas que ça se fasse ressentir.

D’ailleurs, je vous mets au défi de me trouver un exemple où cela pose un souci. Et si c’est le cas, alors castez! Forcez explicitement la conversion. Cela peut être utile dans une structure de donnée, afin de forcer le bon typage au sein des setter (ou mutator pour faire smart). Par exemple, dans le cas d’une entité, les données proviennent généralement d’une base de donnée et arrivent donc toutes sous la forme d’une string.

1
2
3
function setId($id){
    $this->id = (int) $id;
}

Mais, surtout, ne vérifiez jamais le type d’une donnée entrante, en lançant par exemple une exception: vous embrouillerez votre code inutilement. De plus, vous ajoutez autant de conditions supplémentaires que l’on fait appel à votre fonction. C’est du gaspillage. Faites simple et lisible, ne faites que l’essentiel, ne codez que ce pour quoi votre fonction est faite. Ce n’est pas le rôle de votre fonction de tester qu’on l’utilise correctement.

1
2
3
4
5
6
7
8
9
//l'exception ne sert à rien car même si $number n'est pas un int,
//le traitement ne pose aucun problème et convertit implicitement la valeur.

function square($number){
    if(!is_int($number)){
        throw new \InvalidArgumentException('$number doit être un int')
    }
    return $number * $number;
}

Posez-vous la question: « Si je passe un autre type scalaire, mon code marchera-t-il toujours? » Si jamais la réponse est négative, réaménagez votre code, tout simplement. Mais en général, le traitement des arguments force la conversion de manière implicite, comme dans notre exemple. Du moins si, bien sûr, on passe effectivement un scalaire. Dans le cas contraire, PHP criera de toute façon.

Vous verrez, cela ne pose pas de problème. Considérez que vos paramètres sont bons. Vous pouvez, car ils sont interchangeables!

Pas de surcharge… dommage!

Au final, la seule fonctionnalité que l’on puisse regretter, c’est la surcharge paramétrale. Je parle bien sûr de la surcharge classique, pas de la surcharge magique de PHP. Impossible de déclarer deux méthodes avec un nom identique mais des arguments différents.

1
2
3
4
5
6
//PHP considère que foo est déclaré deux fois,
//malgré le type différent de paramètres

function foo(array $arg){}

function foo(Datetime $arg){}

EDIT: A noter que PHP 7 permettra désormais de typer le retour d’une fonction.

Quelques pièges à éviter

Evidemment, il y a aussi des pièges… En voici quelques-uns, sans que la liste ne soit en rien exhaustive!

array, attention aux clés

PHP convertit implicitement les clés d’un tableau soit en int, soit en string. "0", 0, 0.0 s’écrasent les uns les autres. "" et null s’écrasent aussi l’un l’autre.

1
2
3
4
5
6
7
8
9
10
var_dump(array(
    0 => 0,
    '0' => '0', //écrase la valeur précédente
    0.0 => 0.0,  //écrase la valeur précédente
    false => false,  //écrase la valeur précédente  
));
/*
array (size=1)
  0 => boolean false
*/
1
2
3
4
5
6
7
8
var_dump(array(
    '' => '',
    null => null,  //écrase la valeur précédente
));
/*
array (size=1)
  '' => null
*/

Mais bon, si vous codez comme ça…

Trop de cast tue le cast

Les conversions sont parfois à sens unique. Ce n’est pas parce que vous castez le type A vers le type B, que vous reviendrez au résultat initial en recastant dans l’autre sens.

1
2
//après quelques conversions, false vaut true...
var_dump((bool) (array) false); //bool(true)

Fonctions laxistes

Vous cherchez à savoir, grâce à la fonction in_array(), si la string ‘abc’ fait partie d’un tableau ne contenant qu’un int 0. Et PHP vous assure que oui…

1
in_array('abc', array(0)); //boolean true

Avant de se précipiter sur phpwtf pour y rajouter une énième anecdote ne démontrant que l’incompétence de celui qui la poste, essayons de comprendre pourquoi PHP a, en fait, raison.

Par défaut, la comparaison effectuée par in_array() n’est pas stricte. PHP réalise une conversion implicite des valeurs. Or, une conversion vers un entier d’une chaîne de caractères qui ne débute pas par un chiffre donne 0. 0 est donc bien présent.

Quelques fonctions possèdent ce genre de comportement. Il est donc important de forcer la comparaison stricte pour éviter des mauvaises surprises.

1
in_array('abc', array(0), true); //boolean false

null, un cas particulier

null est un type à part. Souvent, il représente l’absence d’objet. Il est une sorte de valeur par défaut d’un objet.

Toutefois, si un paramètre est typé, on ne peut pas passer la valeur null comme argument.

1
2
3
4
5
6
7
class Bar{}

function foo(Bar $bar){
    var_dump($bar);
}

foo(null); //Catchable fatal error: Argument 1 passed to foo() must be an instance of Bar, null given

Sauf si la valeur par défaut de ce paramètre vaut également null.

1
2
3
4
5
6
7
class Bar{}

function foo(Bar $bar = null){
    var_dump($bar); //null
}

foo(null);

Il faut aussi savoir que PHP ne connaît pas d’absence de type, tel que le void de certains langages. Aussi, une fonction retournera null par défaut si aucune autre valeur n’est retournée explicitement.

1
2
3
function foo(){}

var_dump(foo()); //null
1
2
3
4
5
function foo(){
    return;
}

var_dump(foo()); //null

Conclusion

Bien sûr, moi aussi, j’aurais aimé disposer de davantage de possibilités de typage en PHP… Mais, en codant un minimum proprement, on sait déjà faire de belles choses. Le tout étant justement de bien coder, et ce d’autant plus qu’on ne peut pas se reposer sur le typage fort.

Sur ce coup-ci, j’ai été un peu polémique (surtout à l’heure où Hack commence à faire parler de lui pour son typage). Mais très franchement, grâce au type hinting, PHP est à la fois puissant, sûr et souple. C’est une des solutions pour apporter un peu de clarté au code.

Nous avons aussi vu qu’il fallait se prémunir de certains pièges induits notamment par les conversions implicites de PHP. Il nous reste à voir comment nous pouvons gagner davantage en lisibilité. En effet, comme je l’ai dit en introduction, la vraie force du typage faible réside dans la fluidité qu’il procure au langage.

Dans les quelques articles à venir, je vais vous présenter des astuces que j’ai notées pour jouer de cette capacité. Suspens…

10 réflexions au sujet de « Servez-vous du typage faible de PHP! »

  1. Ping : Simplifiez vos conditions

  2. Super article sur le typage en PHP qui est un langage magique qui permet à beaucoup de s’initier à la programmation, et qui ravit beaucoup de professionnels.
    Malgré tout – et je ne crois pas être en désaccord avec l’auteur -, le typage reste un important concept à maitriser pour le stockage et le flux des informations, ainsi que pour éviter des problèmes de mémoire et de performance.

    • oui tout à fait, le typage est vraiment nécessaire dans ce genre de cas. Clairement, si on cherche les performances ou un niveau de sûreté plus élevé, les langages fortement typés resteront incontournables. Mais on n’est pas dans le même domaine de couverture d’application que PHP. (on ne choisit pas PHP pour ses perf… lol!)

      merci pour ta remarque constructive! :)

  3. Rarement vu un article aussi mauvais et faux. Toute l’argumentation est fausse et infondée.

    « Les langages fortement typés, c’est pour les mauvais codeurs! »

    Au secours !!! Le monde est bourré de mauvais codeurs. Paix à leurs âmes.

    • Désolé que tu n’aies pas compris mon article. Et dommage que ton commentaire ne présente absolument aucune forme d’argumentation. On pourrait donc le qualifier avec justesse de mauvais, faux et infondé.

  4. Bon alors clairement, moi on m’a orienté sur cet article juste pour la quote « Mais le fait est qu’avec du typage faible, on gagne en légèreté, en lisibilité. [...] si vous avez besoin d’un typage fort, c’est que vous ne savez peut-être pas si bien coder que ça! ;-)  »
    Bon clairement, non. Je crois que tu n’as pas très bien compris la différence entre typage fort et esthétisme du code. Le fait est qu’avec un langage typé faiblement, effectivement le code est généralement plus beau et agréable à lire. Cela dit les performances s’en font ressentir. Personnellement j’ai besoin de performances. Le Go (« Golang ») est un langage typé fortement. Mais il est néanmoins agréable à lire. Et tu pourra benchmarker les performances entre le go et le php. (Déjà qu’entre le go et le python y’a pas photo)
    Je te conseille de retirer cette phrase. Elle est source de nombreux trolls et qui plus est, totalement infondée et fausse.

    Cette phrase, aux premiers abords, peut-être lue « Tout ceux qui se servent du typage fort ne savent pas coder ». Je pourrai enchainer en disant que tous ceux qui font du php ne savent pas innover par exemple ;)

    PS : Je n’ai pas lu ton article, je m’intéresse guère au php.

    • C’est un peu affligeant que les gens qui troll ne comprennent pas eux même le troll.

      Soit c’est vrai, soit vous n’avez rien compris à l’article. A vous de choisir.

      PS: lis-le avant de penser comprendre et peut-être que tu aurais compris.

      • Je suis désolé mais je n’ai pas le temps de lire la totalité d’un article qui n’a que peu d’intérêt à mes yeux (et ce n’est pas méchant, c’est simplement de l’honnêteté). Je ne faisais que réagir a cette phrase dont, effectivement le principal but était de faire réagir.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>