dimanche 13 juin 2010

Comment supporter __construct() et __destruct() en PHP 4 ?

Ok, rien à voir avec la sécurité, le Reverse Code Engineering ou quoi, mais je m'en fous. C'est mon blog et j'ai bien prévenu que je pouvais publier des trucs sur les polly pockets et les figurines en castor.

Allez, plus sérieusement, j'aimerais partager une astuce - m'en souvenir aussi, en tout cas - de programmation orientée objet en php. Je signale que cette méthode n'est en rien issu d'une masturbation intellectuelle, j'ai trouvé ça dans le framework CakePHP.

Pour se situer dans le contexte



En php4, voici ce qu'on ferait pour déclarer notre définition de classe (le moule à gateau, quoi).

<?php

class Foo {
private $_param = null;

/* Constructeur de la classe */
public function Foo($arg = "") {
$this->_param = $arg;
}
}



Notez que j'ai omis le "?>" à la fin ; en effet, ça permet d'éviter d'avoir des erreurs à la con genre "header already sent by "fichier.class.php" on line [...]. On est sûr de n'avoir que du PHP derrière, pas d'echo d'HTML ni rien.

Et donc, en PHP 5, on a ça :

<?php

class Foo {
private $_param = null;

/* Constructeur de la classe */
public function __construct($arg = "") {
$this->_param = $arg;
}
}



On se sert de la méthode magique __construct(). Notez qu'on peut très bien utiliser aussi le constructeur "Foo()", mais les méthodes magiques n'ont pas été conçues pour rien !

Et pour preuve, en PHP 5, on peut faire ça :

<?php

class Foo {
private $_param = null;

/* Constructeur de la classe */
public function __construct($arg = "") {
$this->_param = $arg;
}

/* Destructeur de la classe */
public function __destruct() {
echo "Bye bye!\n";
}
}



Généralement, on utilise les destructeurs lorsque notre objet est amené à allouer des ressources dynamiquement ; il faut donc les libérer.

Prenons un exemple con : un objet va encapsuler un fichier. Le constructeur va ouvrir ce fichier (ça sera transparent) et le destructeur le fermera sans que nous ayons à écrire :

fclose($handleSurNotreFichier);


Bref, revenons à nos moutons. On est passé à PHP 5 dans la plupart des cas, mais il arrive parfois qu'on ne dispose que d'une version 4 qui ne supporte pas __construct() et __destruct() comme on le souhaiterait. En effet, ces méthodes ne seraient pas magiques et donc pas appelées automatiquement et respectivement lors de la construction et la destruction d'une instance.

Voici comment CakePHP procède : tout d'abord, il définit une class "Mère", je dirais même "Doyenne" donc toutes les autres classes hériteront. Et là, vous comprendrez vite l'intention :

<?php

class Mother {

public function Mother() {
/* On récupère les arguments passés à la fonction
via http://fr.php.net/func_get_args */
$arguments = func_get_args();

/* On vérifie si la méthode __destruct() existe
via http://fr2.php.net/method_exists */
if(method_exists($this, "__destruct")) {
/* Si c'est le cas, on l'assimile en fonction
"callback" de "shutdown, c'est-à-dire qu'elle
sera appelée en fin de vie de l'instance
(http://fr2.php.net/register_shutdown_function)
*/
register_shutdown_function( array(&$this, "__destruct") );
}
/* On appelle notre méthode __construct() avec les arguments passés
avec http://fr2.php.net/call_user_func_array */
call_user_func_array(array(&$this, "__construct"), $arguments);
}

/* Et là, on a notre constructeur qui sera surchargé par les classes
filles */
public function __construct() {
/* Le code de votre choix */
}
}


Désormais, qu'on soit en PHP 4 ou PHP 5, on pourra faire :

<?php

class MaClasse extends Mother {

/* Handle sur un fichier */
private $_handle = null;

/* Constructeur */
public function __construct() {
/* Ouverture d'un fichier */
$this->_handle = @fopen("fichier", "w");
}

/* On peut imaginer des méthodes pour écrire, tout effacer, etc... */

/* Destructeur */
public function __destruct() {
fclose($this->_handle);
}
}


Si ça se trouve j'étais le seul à pas connaître ce tour de passe-passe (& puis tant mieux) mais si y'en a à qui ça peut servir, ben tant mieux. J'ai aucun mérite sur la chose, mais je me dis que partager cette petite astuce qui se retrouve au milieu d'un bazar pointu - c'est comme auditer le code de phpBB, quoi - pourrait servir...

Geo

dimanche 6 juin 2010

Une bonne raison d'utiliser intval()

Me frappez pas. J'ai lâché l'idée de reverser winpcap car au final tout tourne autour d'un driver fait par eux-mêmes. Peut-être que quand j'aurai retrouvé l'envie, je m'y mettrai. Mais là, j'ai la tête à parler de PHP car ça faisait longtemps.

Sur le blog de mon camarade pp^, on peut voir un article fort intéressant sur le contournement de la fonction in_array(). Je ne vais pas m'attarder là-dessus, je vous redirige juste sur son article : http://ppisho.me/hack-web/in_array-probleme-avec-des-nombres/.

J'ai aussi trouvé un problème avec des nombres, hors de la fonction in_array(). M'enfin, c'était pas dur à trouver non plus.

Supposons que vous ayez des droits d'écriture sur $foo :


$foo = 3;
if($foo >= 2) {
echo "Rulez!";
} else {
echo "No...";
}


Évidemment, le code affiche Rulez. Seulement voilà, vous vous en douterez :


$foo = " 3 foobar";
if($foo >= 2) {
echo "Rulez!";
} else {
echo "No...";
}


Ce code affiche aussi "Rulez"... Et pour cause ! PHP, c'est bien, mais c'est pas un langage fortement typé comme le C ou le Java. Tout est chaînes de caractères, en PHP. Imaginez qu'à la place de "foobar" après notre "3", on injecte du code SQL, Javascript, ... Et j'en passe. Bah ça fait bobo. Notez aussi que j'ai mis un espace avant le 3 ; pourvu que ça soit un caractère blanc, donc tabulation, retour à la ligne, etc, en fait.

Bon, ok, c'est pas une découverte révolutionnaire et tout le monde sait comment éviter ça.

Avec intval(), notre variable aura toujours un nombre entier mais la condition sera quand même vérifiée :

$foo = intval(" 3 foobar");
if($foo >= 2) {
echo "Rulez! (\$foo = ".$foo.")\n";
} else {
echo ":(";
}


Résultat :

Rulez! ($foo = 3)


Ca sera tout.

Geo