Posts tagged “php”.

Cannot re-assign $this (en théorie)

Décidemment le modèle objet de PHP n’est pas mon meilleur ami. Après avoir eu la suprise de découvrir que certaines méthodes ne peuvent pas lancer d’exception, voila que $this change mystérieusement de valeur.

Comme le suggère le dollar dans le nom, $this est bien une simple variable. Donc à ce titre elle peut surement être modifiée, non ? Heureusement, la changer par une assignation directe est impossible, et conduira à une erreur fatale. Ainsi, le code suivant affichera l’erreur Cannot re-assign $this.

class ThisIsFun {
	function reassignThis()
	{
		$this = 'something else';
	}
}

$obj = new ThisIsFun();
$obj->reassignThis();

Mais certaines fonctions, comme extract peuvent aussi manipuler les variables (je l’utilise pour exporter certaines variables vers la vue dans le cadre d’une architecture MVC). Résultat, avec un code comme ci-après, $this est modifié…

class ThisIsFun {
	function reassignThis($vars)
	{
		var_dump($this); // object(ThisIsFun)[1]
		extract($vars);
		var_dump($this); // string 'something else' (length=14)
	}
}

$obj = new ThisIsFun();
$obj->reassignThis(array('this' => 'something else'));

Heureusement la modification n’est effective que dans le contexte de la fonction, mais quand même, c’est plutôt étonnant que ça soit possible. Pour l’éviter il faudrait utiliser le paramètre optionnel supplémentaire extract_type qui contrôlle la gestion des collisions.

Du coup je me suis amusé un peu à chercher d’autres moyens de modifier $this, même si je ne sais pas encore trop à quoi ça pourrait bien servir… Allez, avec une référence c’est élégant :)

class ThisIsFun {
	function reassignThis()
	{
		var_dump($this); // object(ThisIsFun)[1]
		$that = & $this;
		$that = 'references are cool';
		var_dump($this); // string 'references are cool' (length=19)
	}
}

$obj = new ThisIsFun();
$obj->reassignThis();

__toString() must not throw an exception

La bonne blague PHP du jour concerne la méthode “magique” __toString, qui est appellée automatiquement lorsqu’un objet doit être converti en chaine de caractère. Pour je ne sais quelle raison, PHP à partir de la version 5.2 retourne une erreur fatale lorsque une exception est lancée depuis cette méthode…

Ainsi, le code suivant n’affichera jamais “Catched”.

class Foo {
	public function __toString() {
		throw new Exception('woupsi !');
	}
}

try {
	echo new Foo();
} catch (Exception $e) {
	echo 'Catched';
}

Au lieu de ça, j’ai le droit à :

Fatal error: Method Foo::__toString() must not throw an exception

Je trouve ça vraiment étonnant qu’une méthode particulière ne puisse pas lancer d’exception, d’autant qu’entre une erreur fatale et une exception je ne vois pas vraiment la différence. Si j’étais mauvaise langue, je dirais qu’il s’agit d’un hack placé là parceque l’implémentation des exceptions en PHP 5 et/ou de la méthode __toString est moisie, mais je ne le ferais pas.

Evidemment, ce n’est précisé nulle part dans la documentation. Il y a d’ailleurs un bug report ouvert depuis l’année dernière (sic) demandant une mise à jour de la documentation pour préciser cette feature

Bon, comme j’ai vraiment besoin d’utiliser __toString et que j’ai aussi besoin de traiter les erreurs, voici le workaround (pas très propre) que je vais utiliser en attendant de trouver mieux :

class Foo {
	public function __toString() {
		try {
			return $this->functionThatMayThrowAnException();
		} catch ( Exception $e ) {
			trigger_error($e->getMessage(), E_USER_ERROR);
		}
	}
}

Utilisation de l’opérateur “or”

Recemment je me suis heurté à quelques soucis avec l’opérateur “or”, notamment lorsqu’il est utilisé en dehors d’une condition. Bien que l’ordre de précédence des opérateurs soit défini dans la documentation de PHP (et commun à beaucoup d’autres langages), il m’arrive encore de me faire surprendre. Aussi, voici une petite clarification.

Un exemple très répandu de l’utilisation de “or” en dehors d’une condition est lorsqu’il est utilisé conjointement avec die() celui-ci :

doSomething() or die();

Si doSomething() retourne “vrai”, alors l’évaluation s’arrette, parceque l’opération “or” retourne toujours “vrai” si au moins un des éléments est “vrai”, donc le moteur optimise en passant les tests inutiles.

Si doSomething() retourne faux, alors l’évaluation continue, et die() est executé (adieu petit script).

More… »

Une version optimisée pour PHP de JSMin

Les Javascripts permettent de faire tout pleins de choses sur un site web, c’est cool. Mais à force d’empiler les librairies, les frameworks et autres fonctions, on peut vite se retrouver avec deux kilo-tonnes de script, ce qui n’est ni très agréable à charger et ni très léger pour la bande passante.

Heureusement, plusieurs librairies (plus ou mois efficaces) existent et permettent de compresser les scripts. JSMin est l’une d’entre elle.

More… »

OVH et les sessions

Voila, c’est officiel, je ne supporte plus l’hébergeur français OVH.com. Propriétaire d’un 90plan chez eux (hebergement mutualisé labellisé « pro ») depuis plusieurs mois, je subis à longueur de temps les bugs ou les modifications intempestives de leur plateforme : sous-domaines qui ne fonctionnent pas, ftp capricieux, scripts php mis en cache sans avoir rien demandé, etc. Leur dernière lubie : désactiver la fonction PHP session_save_path (qui permet, rappellons le, de spécifier le dossier dans le lequel sont stockées les sessions), évidemment sans prévenir personne.
More… »

Mon premier programme C/PHP

Comment embarquer PHP dans un programme C de la manière la plus basique possible.

Hello world

En guise d’”Hello World”, nous allons essayer d’executer la commande phpinfo().

Avant tout, il faut initialiser le module php embed. Tout ceci n’étant absolument pas documenté, je ne saurais dire à quoi correspondent les paramètres.

static char *argv[2] = {"monboprog", NULL};
if ( php_embed_init(1, argv PTSRMLS_CC) == FAILURE ) {
	puts ("Impossible d'initialiser PHP");
	return -1;
}

Maintenant on peut executer une commande. On utilise zend_eval_string qui prend comme premier paramètre la commande à executer. Je ne sais pas à quoi correspond le deuxième (je laisse NULL). Quant au dernier, apparement on peut mettre n’importe quoi… Allez comprendre !

Zend (le parseur php) fournis des macros imitant le try-catch du C++, permettant de recuperer les erreurs de php. Il n’y a donc qu’à les utiliser. Et voila ce que ça donne :

zend_first_try {
	if ( zend_eval_string("phpinfo();", NULL, "php embed roulez") == SUCCESS )
		puts("Commande executée avec succès");
	else
		puts("Impossible d'executer la commande");
} zend_catch {
	printf ("Exception %d", EG(exit_status));
}
zend_end_try();

Enfin, on arrette php avec la commande suivante :

php_embed_shutdown(TSRMLS_C);

Il ne manque plus qu’à inclure php_embed.h, et on peut essayer de le compiler. Le programme complet d’exemple est disponible par ici

Compilation

Ca c’est la partie la plus galère, surtout quand rien n’est documenté. Je tiens d’ailleurs à remercier kermit, une star du Makefile, sans qui je ne serais sans doute jamais arrivé à compiler cette saloperie de programme.

En fait, php_embed.h demande pleins d’autres fichiers d’entetes, situés un peu partout dans les sources de PHP. Il faut donc rajouter un paquet de paramètres d’inclusion à gcc. Dans un makefile, ça donne :

# chemin des sources de php
PHPPATH=/home/cgo2/documents/prog/php/php-5.0.2
PHPFLAGS=-I$(PHPPATH) -I$(PHPPATH)/Zend -I$(PHPPATH)/TSRM -I$(PHPPATH)/sapi/embed

Pour le linkage, il faut utiliser la lib que l’on a compilé :

LDFLAGS=-L$(PHPPATH)/libs LIBS=-lphp5

Le makefile pour ce programme d’exemple n’est pas encore parfait, puisque trop de paramètres sont hardcodés, mais bon…

Exécution

Premier test :

 $ ./monboprog ./monboprog: error while loading shared libraries: libphp5.so: cannot open shared object file: No such file or directory 

Pas grave, il suffit d’ajouter le chemin de la libs dans la variable d’environnement qui va bien :

 $ export LD_LIBRARY_PATH=~/documents/prog/php/php-5.0.2/libs/ 

Et voila :)

Conclusion

Il reste encore beaucoup à faire pour pouvoir embarquer facilement php : faciliter la compilation et le linkage (tout le monde n’a pas les sources de php sous la main), et également l’execution. Quant au code, dans un premier temps un découpage en fonctions permettant d’eviter de manipuler Zend directement s’impose. Il faut ensuite voir comment inclure des fichiers php, exporter des fonctions C pour les utiliser dans les scripts, etc… Mais au moins avec cet exemple vous pourrez dire : “moi aussi j’ai réussi à embarquer php” !

Quelques liens

  • LE programme sans lequel on aurait rien reussi : le module php-irssi qui permet de script irssi avec php. Malheureusement il n’y a aucun site officiel, et le projet semble mort. On peut encore recuprer les sources sur le CVS de php
  • Un PDF interressant par l’auteur de php-irssi

Compiler PHP avec le support embed

Étant donné l’absence complète de documentation sur le module “embed” de php, les articles ont tous été écrit à partir d’un travail de reverse engineering effectué par kermit et moi-même (cgo2), et donc peuvent se révéler incorrects et/ou incomplets. Évidemment, tout ceci est fait sous Linux.

Première chose, nous allons récupérer les sources de PHP. A l’heure où j’écris cet article, la version la plus récente est la 5.0.2, disponible par ici : http://www.php.net/downloads.php

Après avoir décompressé l’archive, il faut configurer PHP avant la compilation. Pour l’instant on ne compile que la base avec le module “embed” (pour les autres modules, je vous conseille d’aller lire la doc)

$ ./configure --disable-all --enable-embed
$ make

La lib libphp5 (.so et .la) est placé dans le repertoire libs/, nous en aurons besoin par la suite pour le linkage.

Mais il va falloir également beaucoup de headers pour la compilation, gardons donc les sources de php intactes pour l’instant.

Tout est prêt pour écrire notre premier programme C/PHP