Manuel de référence → Framework

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 avec echo ;
  • ERROR_TRIGGER : lance une erreur utilisateur de paramètre E_USER_WARNING par défaut avec la fonction trigger_error ;
  • ERROR_DIE : tue le message avec exit.

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.