Chapitre 3. Framework
Le fichier Framework
Chaque paquetage de Hoa a un fichier en commun :
Framework.php, qui contient
Hoa_Framework.
Ce fichier se trouve à la racine du framework, et doit être le premier à être inclus dans chaque paquetage, car il est le fichier central du framework. Chaque paquetage dépend de ce fichier, il est le pivot principal du framework car c'est lui qui permet la communication entre les différents paquetages. Il met également à disposition une gestion simplifiée des constantes, et il aide à résoudre des problèmes de dépendances entre les paquetages.
On appelera toujours ce fichier de cette façon :
<?php /** * Commentaires présentant le paquetage. */ /** * Hoa_Framework */ require_once 'Framework.php';
Grâce à la redéfinition des chemins d'inclusions (voir
la section intitulée « Modifier les chemins d'inclusion »), le fichier
Framework.php est accessible depuis n'importe quel
paquetage de façon simple et naturelle.
On insiste sur le fait qu'il faille utiliser la fonction
require_once,
même si cette fonction est plutôt gourmande en
ressources[3],
on l'utilisera systématiquement. Le fichier
Framework.php tente de minimiser les pertes en
mémoires de ces fonctions. On explique comment, plus loin dans le document.
Constante du Framework
Le paquetage Hoa_Framework ose définir quelques
constantes utiles pour le reste du framework.
Séparateur de dossiers
PHP met à notre disposition une constante très
pratique : DIRECTORY_SEPARATOR. Hélas, c'est bien
long à taper. Comme un bon programmeur est un programmeur fainéant, on a
créé un alias vers cette constante : DS ; de cette
façon :
define('DS', DIRECTORY_SEPARATOR);
On insiste sur le fait que cette constante n'est qu'un alias.
PHP propose cette constante car les séparateurs de dossiers diffèrent selon les systèmes sur lesquels on travaille.
Il faut s'imaginer une arborescence comme un arbre, où chaque branche donne lieu sur de nouvelles branches, à travers des nœuds, jusqu'à atteindre les feuilles. Comme le caractère symbolisant un nœud est différent selon le système, il est nécessaire d'utiliser cette constante, cela assure la portabilité du projet.
Ainsi, un chemin s'écrira de la façon suivante :
$chemin = 'dossierA' . DS . 'dossierB' . DS . 'dossierC'; $fichier = $chemin . DS . 'monFichier.php';
Séparateur de chemins
De la même manière, il est nécessaire d'avoir une constante symbolisant les chemins ; caractère lui aussi différent selon les systèmes.
PHP propose la constante
PATH_SEPARATOR. Pour les mêmes raisons que sa jumelle, on a défini un
alias :
define('PS', PATH_SEPARATOR);
Cette constante est également à utiliser à chaque fois que l'on veut utiliser un séparateur de chemins.
Ainsi, si l'on reprend l'exemple de la section intitulée « Modifier les chemins d'inclusion » et que l'on veut y ajouter
un autre chemin plus loin dans le script, la constante
PS sera définie, alors :
set_include_path('./Autre_Chemin' . PS . get_include_path());
Fin de ligne
Un fichier non vide contient généralement des lignes. Ces lignes ont un début et une fin. La fin d'une ligne peut être déduite en fonction de sa longueur, ou alors à partir d'une suite de symboles.
On a pour habitude de définir la fin de ligne à partir du symbole appelé CRLF. Le CRLF est un caractère spécial[4]. Ce n'est pas un caractère ASCII, mais l'association de 2 caractères : 13 et 10, respectivement pour \r et \n. Le \r correspond au retour chariot[5], et le \n correspond à la fin de la ligne.
Mais comme beaucoup de paramètres, celui-ci dépend du système sur
lequel on travaille. En effet, sur Unix, on utilise plutôt
LF, sous Mac OS, CR, et sous Windows,
CRLF. D'où l'importance d'introduire une constante. Cette
constante aurait très bien pu s'appeler
EOL, on l'accorde, mais on a préféré
être générique est l'appeler CRLF pour éviter les confusions
avec EOF etc.
Il est possible de la rédéfinir en cas de besoin. Pour cela, il faut
ouvrir le fichier Framework.php, et modifier sa valeur.
Elle a été initialisée à \r\n :
define('CRLF', "\r\n");
Racine du framework
On a parfois besoin de savoir où se trouve le framework. On met
donc à disposition la constante FW_BASE qui vaut la
valeur du dossier de la
constante magique
__FILE__. Cette constante retourne le chemin
complet et le nom du fichier courant. On travaille avec une version 5
de PHP, __FILE__ retourne donc un
chemin absolu (ce qui n'était pas le cas des versions inférieures à
4.0.2). On se débarasse du nom du fichier à l'aide de la fonction
dirname :
define('FW_BASE', dirname(__FILE__));
Système opérateur
Toutes les commandes du terminal, ou les fonctions de
PHP ne sont pas accessibles selon le système sur
lequel on travaille. Certains systèmes sont très différents des autres.
Il est donc bon de savoir si on se trouve notamment sur Windows[6]. C'est le but de la constante
OS_WIN qui vaut true si le
système opérateur est Windows, false sinon :
define('OS_WIN', !strncasecmp(PHP_OS, 'win', 3));
Ainsi, on aura quelque chose de similaire :
if(true === OS_WIN) { // Une action propre à Windows. } else { // L'équivalence pour tous les // autres systèmes. }
Simplification d'écriture de constantes
Hoa_Framework propose une fonction pratique qui permet de
ne pas se poser de questions quant à l'existence d'une constante.
On peut lire le manuel sur la forme des constantes pour mémoire. On se rappelle que la forme des constantes peut avoir pour expression régulière :
[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*
Quand on veut déclarer une constante, il suffit d'invoquer la fonction
_define. La fonction teste si la constante est
préalablement définie ; si oui, alors on retourne
false, sinon on la définit avec un nom, une valeur
et une insensibilité de casse. Elle a un comportement strictement identique à
la fonction define
de PHP lors de la définition (à une insensibilité de
casse près).
Ainsi, toutes les constantes peuvent être déclarées de la façon suivante :
_define('BONJOUR', 'Bonjour tout le monde !');
Dans ce cas, l'appel de BONJOUR retournera la
chaîne Bonjour tout le monde !, et l'appel de
Bonjour ne retournera rien, mais soulèvera une erreur. En effet,
l'insensibilité de casse est mise à faux par défaut, i.e.
la casse est sensible, elle doit être identique. Si on veut que la casse soit insensible,
alors il faut déclarer :
_define('BoNjOur', 'Re-bonjour tout le monde !', true);
Alors, appeller BONJOUR,
Bonjour etc. retournera la chaîne
Re-bonjour tout le monde !.
Importation
Chemin statique
PHP propose quatre fonctions qui permettent d'inclure ou de réquisitionner un fichier.
require et include sont identiques à
une gestion d'erreur près. En effet, require génère
une erreur fatale contrairement à include qui ne génère
qu'une alerte.
Si une erreur intervient pendant le déroulement de l'application,
require arrêtera son exécution, alors
qu'include affichera seulement une erreur, et
l'application continuera son exécution.
Ces mêmes fonctions existent suffixées de
_once. Cela signifie que si un fichier a déjà été
chargé, alors il ne le sera pas à nouveau. Cette fonctionnalité est très
pratique, mais elle consomme de la mémoire.
Hoa_Framework se propose donc de palier ce problème
de mémoire en tenant lui-même un historique des inclusions.
Pour inclure ou réquisitionner un fichier, on travaille avec
l'aide de la fonction import (qui utilise
require, important pour le type d'alerte).
Plusieurs choses sont à noter. La plus importante est l'écriture des
chemins. Dans la section intitulée « Paquetage », on voit comment écrire le
nom d'un paquetage. L'importation de ce dernier se fait de façon tout
à fait naturelle en remplaçant les traits de soulignement par des
points, et en supprimant la racine Hoa.
Ainsi Hoa_Controller_Dispatcher_Abstract
devient le chemin Controller.Dispatcher.Abstract.
Par exemple :
<?php /** * Commentaires sur le paquetage */ /** * Hoa_Framework */ require_once 'Framework.php'; /** * Hoa_Controller_Dispatcher_Abstract */ import('Controller.Dispatcher.Abstract'); /** * Hoa_Autre_Paquetage */ import('Autre.Paquetage');
Ce comportement peut rappeler les importations de Java, mais la
fonction import de Hoa apporte des petites choses
supplémentaires.
Seulement pour information, la fonction import
est en réalité un alias vers la méthode statique
Hoa_Framework::import. On a créé cet alias, car
il est plus rapide et agréable de n'écrire
qu'import.
Chemin étendu
Il arrive qu'on ait besoin de tous les fichiers d'un paquetage. Si
ce dernier comporte plus de 10 fichiers, ce sera long et pénible de tous
les écrire. On utilise alors le caractère étoile pour dire :
tous. Ainsi Filter.* va
inclure Filter.Alpha,
Filter.AlphaNumeric,
Filter.Array, Filter.Blank,
etc. Il ne faut pas en abuser ; il est préférable
d'inclure strictement les paquetages dont nous avons besoin. En général,
un paquetage règle ses dépendances seul, i.e. il
importe les paquetages et fichiers dont il a besoin pour fonctionner.
Néanmoins, cette fonctionnalité peut s'avérer pratique.
/** * On appelle tous les fichiers du paquetage "Paquetage". */ import('Paquetage.*');
On précise — même si c'est un intuitif — que l'étoile ne peut être que la dernière partie du chemin, sinon, cela n'aurait pas de sens.
Chemin substitué
Il y a aussi l'auto-complétion des chemins. Il arrive qu'un paquetage
s'appelant A comporte une seule classe
A, ou alors la classe d'entrée s'appelle
A (en effet, les classes d'entrée des paquetages
comportent le même nom que le paquetage). Il est donc lassant d'écrire
UnLongNomPourUnPaquetage.UnLongNomPourUnPaquetage,
on préférera écrire UnLongNomPourUnPaquetage.~. Le
tilde signifie que le nom du fichier est le même que le nom du
paquetage, par exemple : Translate.~, équivaut à
Translate.Translate (soit
Translate/Translate.php).
/** * On appelle le fichier "Paquetage" du paquetage "Paquetage". */ import('Paquetage.~');
Le tilde prend tout son sens uniquement pour la dernière partie du chemin. Si on place le tilde avant la dernière partie, il n'aura aucun effet à part produire une erreur. On l'utilise principalement pour faciliter l'appel aux classes d'entrée.
Gestionnaire d'exceptions
PHP 5 nous offre la possibilité d'utiliser les exceptions. Une
exception peut s'apparenter à un objet erreur. Il peut être lancé à l'aide
de l'instruction throw, et attrapé à l'aide de
l'instruction catch. Une exception lancée remonte
dans le code jusqu'à arriver dans un bloc catch où elle
sera attrapée.
Le paquetage Hoa_Exception est le plus haut du
framework.
Toutes les exceptions du framework sont des exceptions étendues,
i.e. elles héritent toutes de
Hoa_Exception.
Hoa_Exception est même une exception
étendue de la classe Exception de
PHP. Ainsi, toute exception peut se capturer de cette
façon :
try { // Un code qui est susceptible de // jeter une exception. } catch ( Hoa_Exception $e ) { $e->raiseError(); }
Filet à exception
PHP propose une fonction utilisateur qui permet
la gestion des
exceptions. Hoa s'en sert pour rediriger toutes les exceptions
non capturées vers Hoa_Exception::handler.
De ce fait, les exceptions non capturées seront toujours attrapées
et — par défaut — affichées. On précise que c'est par défaut, car les
exceptions sont transformées en messages, qui peuvent être retournés,
affichés, être lancés avec la fonction trigger_error, ou
être tués avec exit.
Exception étendue
Chaque paquetage contient au moins une exception qui est étendue à
l'exception mère Hoa_Exception. Par exemple, le nom
de l'exception du paquetage Mail est
Hoa_Mail_Exception.
On sait alors d'où vient l'exception, et on peut tracer son parcours. On peut également créer des exceptions personnalisées, les faire dévier etc. La gestion des exceptions est très libre et souple.
Construction d'exceptions
La construction des exceptions est un peu particulière dans Hoa, elle ajoute des petites fonctionnalités.
Une exception est constituée tout d'abord d'un message, qui est le
premier argument.
Ce message sera formaté, il peut donc contenir tous les caractères de
formatage de texte. La liste des
options de formatages se trouve sur la
description de la fonction sprintf. Hoa n'utilise
pas sprintf pour formater le texte, mais
vsprintf, qui formate
une chaîne à partir d'un tableau. Le tableau est passé en troisième
argument de l'exception ; on y reviendra.
Le second argument est le code de l'exception, il est écrit arbitrairement. On a pris pour habitude d'incrémenter les exceptions à l'intérieur d'une classe, de cette manière, une exception est identifiée par son nom, et un code.
On revient sur le tableau contenant la liste des valeurs pour formater le message de l'exception. Si on passe une chaîne, elle sera transtypée en tableau, sinon on passe un tableau uni-dimensionnel. Le troisième paramètre est optionnel. Par exemple :
/** * On définit notre propre exception. */ class MonException extends Hoa_Exception { } /** * On jette une exception toute simple. */ throw new MonException('Attention, une exception, ' . 'derrière vous !', 0); /** * On jette une exception avec un message formaté simple. */ throw new MonException('%s le crabe.', 1, 'Billy'); /** * On jette une exception avec un message formaté. */ throw new MonException('La somme de 1 à %d donne %d, ' . '%2$d étant un nombre d\'Armstrong, ' . 'et %1$d étant un nombre premier.', 2, array(17, 153)); // Le message sera (sans les retours à la ligne) : // La somme de 1 à 17 donne 153, // 153 étant un nombre d'Armstrong, // et 17 étant un nombre premier. /** * On jette une exception courante. */ throw new MonException('Le fichier %s n\'existe pas.', 3, $nomDuFichier);
Méthodes d'exceptions
Une fois une exception construite et lancée, on se doit de la capturer :
try { // Code qui jette une exception. } catch ( Hoa_Exception $e ) { // On l'a capturée. }
Une fois l'exception capturée, on peut lui appliquer plusieurs méthodes pour obtenir différents renseignements.
Les méthodes héritées de la classe Exception
On a le droit d'appeler toutes les
méthodes de la classe Exception de PHP,
i.e. :
getMessage: qui retourne le message de l'exception ;getCode: qui retourne le code de l'exception ;getFile: qui retourne le nom du fichier depuis lequel l'exception a été lancée ;getLine: qui retourne le numéro de ligne dans le fichier depuis lequel l'exception a été lancée ;getTrace: qui retourne un tableau de backtrace ;getTraceAsString: qui retourne le chaîne formatée de trace.
En plus de ces méthodes, Hoa_Exception propose deux
méthodes supplémentaires.
La méthode __toString
La première est la redéfinition de __toString.
De cette façon, si on affiche l'objet directement, une chaîne de caractères
représentera l'objet. Ainsi, le code suivant ne génèrera pas d'erreur :
try { // Code qui jette une exception. } catch ( Hoa_Exception $e ) { echo $e; }
La méthode raiseError
La seconde fonction, raiseError, permet de
gérer la façon dont le message va être utilisé. On avait dit que les
exceptions étaient transformées en messages, qui peuvent être retournés,
affichés, être lancés avec la fonction trigger_error, ou être tués
avec exit.
Par défaut, le message est affiché :
try { // Code qui jette une exception. } catch ( Hoa_Exception $e ) { // Le message sera affiché. $e->raiseError(); }
Pour traiter le message autrement, on a à notre disposition les constantes de classe :
ERROR_RETURN: retourne simplement le message ;ERROR_PRINT: affiche le message avececho;ERROR_TRIGGER: lance une erreur utilisateur de paramètreE_USER_WARNINGpar défaut avec la fonctiontrigger_error;ERROR_DIE: tue le message avecexit.
Ainsi, si on veut tuer le message, on aura :
try { // Code qui jette une exception. } catch ( Hoa_Exception $e ) { // Fin d'exécution après l'affichage // du message. $e->raiseError(Hoa_Exception::ERROR_DIE); }
On a vu qu'on pouvait aussi lancer une erreur utilisateur à l'aide de
trigger_error. Cette
fonction prend un paramètre qui définit le type d'erreur. La fonction
Hoa_Exception::raiseError définit le type d'erreur
E_USER_WARNING par défaut, mais il est possible d'en
proposer d'autres à l'aide du second paramètre de
raiseError.
On aura la liste des
constantes pré-définies précisant les types d'erreurs dans le
manuel de PHP.
Par exemple, si on veut générer une notice, on aura :
try { // Code qui jette une exception. } catch ( Hoa_Exception $e ) { // Une erreur utilisateur de // type notice. $e->raiseError(Hoa_Exception::ERROR_TRIGGER, E_USER_NOTICE); }
Les exceptions peuvent être manipulées comme bon nous semble.
Registre de fonctions de clôtures
Au début de son développement, Hoa était en PHP
4, il n'y avait pas de méthodes destructrices pour les objets
(__destruct).
On avait alors programmé un registre
de fonctions de clôtures. Ces fonctions se lançant une fois tous les
autres scripts terminés (fin d'exécution).
Dans sa version PHP 5, Hoa a conservé ce registre de fonctions de clôtures car il peut avoir d'autres buts : émulation de destructeur pour les objets statiques, écriture de journaux, etc.
La méthode pour accéder au registre est statique. Il n'est pas possible d'enregistrer plusieurs fois la même méthode. On enregistre le nom de la classe contenant la méthode, et le nom de la méthode :
<?php /** * Hoa_Framework */ require_once 'Framework.php'; /** * On a une classe qui va être appelée à la fin de l'exécution. */ class MaClasse { public function uneMethode ( ) { echo 'Je suis exécutée !' . "\n"; } } /** * On enregistre la méthode. */ Hoa_Framework::registerShutdownFunction('MaClasse', 'uneMethode'); echo 'On exécute l\'application normalement.' . "\n";
On obtiendra :
On exécute l'application normalement. Je suis exécutée !
L'ordre d'exécution des méthodes de clôtures se fait dans le même
ordre dans lequel elles ont été déclarées dans le registre. Si on déclare
methodeA avant methodeB, alors
methodeA sera la première à s'exécuter. On retrouve
le comportement d'une file FIFO.
registerShutdownFunction retourne
true en cas de succès, false si
l'enregistrement n'a pas pu être fait, ou qu'il a déjà été fait.
[3] On ne saurait quantifier les performances de cette
fonction vis-à-vis de ses soeurs (require et
include).
Mais le fait est que ces fonctions consomment plus de mémoire.
[4] On a pour habitude de dire que c'est un caractère spécial, mais il faut savoir que c'est en réalité une suite de caractères.
[5] Lorque l'ordinateur n'était pas encore aussi répandu qu'aujourd'hui, les machines à écrire effectuaient un retour chariot physique pour la fin de ligne, le terme est resté.
[6] On ne tente pas de recréer ici une polémique déjà vieille, mais il a été remarqué à l'usage que Windows était le plus gênant dans le développement d'application ; ce n'est qu'une constatation.

