Manuel d'apprentissage
⁂
Exploration du noyau
Le noyau est la partie indispensable de Hoa. C'est le noyau qui assure la cohésion et la modularité de Hoa. Il s'assure également de la compatibilité entre les versions de PHP et de Hoa à travers des mécanismes très simples.
Table des matières
Introduction
Le noyau est la seule partie de Hoa qui est indispensable, i.e. nécessaire dans toutes les situations. En effet, c'est le noyau qui s'assure de la cohésion et de la modularité entre les différentes bibliothèques de Hoa, ainsi que de la compatibilité entre les différentes versions de PHP et de Hoa. Ainsi, si vous utilisez les mécanismes proposés par le noyau, vous vous assurez une migration plus aisée entre les versions de Hoa et de PHP.
Le noyau s'instancie à l'aide d'un seul fichier
Core/Core.php. Ce dernier instanciera le reste du noyau au
besoin et dans de bonnes conditions.
<?php
require '/usr/local/lib/hoa/Core/Core.php';
Le noyau ne peut être inclus qu'une seule fois. Si nous essayons de l'inclure une deuxième fois, le programme quittera avec une erreur. C'est le seul et unique cas où Hoa va quitter le programme.
Toutes les classes appartenant à l'espace de nommage
Hoa\Core n'ont pas à être importées manuellement, elles sont
toujours présentes.
Carte du noyau
Le noyau est constitué de plusieurs couches :
- Consistency est la couche qui gère la cohésion entre les bibliothèques à travers les importations, les chargements, les appels etc. ;
- Event est la couche qui introduit les événements dans PHP ; nous distinguerons deux catégories d'événements ;
- Exception est la couche qui gère les exceptions, unifie les erreurs etc. ; nous distinguerons trois catégories d'exceptions ;
- Protocol est la couche qui introduit le protocole
hoa://, très utile pour abstraire l'accès aux ressources ; - Parameter est la couche qui permet de paramétrer des classes et des bibliothèques ;
- Data est la couche qui permet de manipuler des données polymorphiques avec de bonnes performances.
Nous présentons dans ce chapitre les quatre premières couches. Les couches restantes seront présentées dans les chapitres suivants, cela dans un but pédagogique.
Consistency
La classe Hoa\Core\Consistency porte le mécanisme
d'importation et de chargement des classes.
Ce mécanisme est basé sur la notion de familles de classes et
d'espaces de nommage. Quand nous souhaitons utiliser une classe, nous allons
commencer par préciser à quelle famille elle appartient, puis nous allons
l'importer. La sélection de la famille se fait à l'aide de la fonction
from. Cette fonction est un alias vers la méthode
Hoa\Core\Consistency::from, pour plus de facilité d'écriture et
de lecture. Ainsi, nous choisissons la famille Hoa pour effectuer
une importation :
from('Hoa')
-> import(…);
Il existe deux familles par défaut (d'autres peuvent être ajoutées) :
Hoapour les bibliothèques standards de Hoa ;Hoathispour les bibliothèques utilisateurs.
Chemin d'importation
Un chemin d'importation exprime un chemin vers un fichier contenant la
classe à importer. Le séparateur entre chaque partie du chemin est le point
(symbole « . ») et l'extension du fichier doit être
« .php » sans être précisée dans le chemin. Ainsi, si nous
voulons inclure la classe Hoa\Router\Http située dans le fichier
Library/Router/Http.php, nous écrirons :
from('Hoa')
-> import('Router.Http');
Nous comprenons que la famille Hoa correspond à l'espace de
nommage racine \Hoa et est contenue dans le dossier
Library/. Tout ceci est bien évidemment modifiable, nous le
verrons plus tard.
Nous sommes capable d'enchaîner les importations et de changer de famille de cette manière :
from('Hoa')
-> import('Router.Http')
-> import('Dispatcher.Basic')
-> import('File.Read');
from('Hoathis')
-> import('My.Own.Library');
Les chemins peuvent être un peu plus sophistiqués à l'aide de deux
opérateurs : de répétition (symbole « ~ ») et étoile (symbole
« * »).
L'opérateur de répétition vaut la partie précédente du chemin. Par exemple,
le chemin Xml.~ est équivalent à Xml.Xml, ou encore
Stream.I~.In est équivalent à Stream.IStream.In. Cet
opérateur est utile par exemple pour atteindre la classe
d'entrée d'une bibliothèque, i.e. une classe qui porte le même nom
que la bibliothèque (c'est le cas de Hoa\Xml qui se trouve dans
le fichier Library/Xml/Xml.php).
L'opérateur étoile (qui fait référence à l'étoile de
Kleene) signifie « tout ». Par exemple, le chemin
Stream.I~.* correspond à tous les chemins possibles depuis le
point Stream.IStream. L'opérateur étoile n'est pas récursif,
i.e. il ne comprend qu'un seul niveau.
Néanmoins, nous déconseillons l'utilisation excessive de l'opérateur étoile. En effet, il est utile voire inévitable dans certaines situations, mais il est toujours préférable de gérer les importations au cas par cas, quand c'est possible, afin de toujours savoir ce que nous manipulons.
Pré-chargement, chargement et auto-chargement
Lorsque nous importons une classe, elle n'est pas chargée. Nous disons qu'elle est pré-chargée, i.e. qu'elle n'est pas chargée tant que nous n'en avons pas besoin. Le fichier et la classe qu'il contient, seront chargés en toute dernière minute afin de ne pas surcharger la mémoire et d'éviter des accès disque inutiles.
Le chargement s'effectue donc à travers le mécanisme d'auto-chargement que propose PHP. Les étapes sont les suivantes :
- nous avons besoin d'une classe qui n'est pas chargée ;
- si elle n'est pas pré-chargée, une exception est levée ;
- si elle est pré-chargée, nous la chargeons.
Cette suite d'opérations n'a lieu qu'une seule fois par classe.
Toutefois, si nous ne souhaitons pas pré-charger mais charger la classe
lors de l'importation, nous pouvons utiliser le second paramètre de la méthode
import qui est un booléen : true pour charger,
false pour pré-charger. Ainsi :
from('Hoa')
-> import('Router.Http', true) // chargée
-> import('Dispatcher.Basic'); // pré-chargée
Importer depuis plusieurs familles
Il arrive parfois que Hoa cherche d'abord les classes dans une autre
famille que la sienne. Vous pouvez également utiliser ce mécanisme :
imaginons que vous souhaitiez utiliser soit votre propre classe
Hoathis\Socket\Server, soit si elle n'est pas présente, la
classe standard Hoa\Socket\Server, alors vous feriez :
from('Hoathis or Hoa')
-> import('Socket.Server');
Ce mécanisme va d'abord chercher à importer Socket.Server
dans la famille Hoathis. S'il n'y parvient pas (par exemple
si la classe n'existe pas), alors il cherchera dans la famille
Hoa.
Pour savoir quelle famille a été sélectionnée, vous pouvez utiliser un
troisième paramètre de la méthode import, en référence :
from('Hoathis or Hoa')
-> import('Socket.Server', false, $family);
var_dump($family);
// Will output:
// string(7) "Hoathis"
// or:
// string(3) "Hoa"
Ce mécanisme est surtout très utile avec le dynamic new.
Dynamic new
La couche Consistency introduit un mécanisme appelé dynamic new, comprendre un moyen d'importer, de charger et d'instancier des classes dynamiques. Ceci est une implémentation légère et efficace du modèle de conception Fabrique abstraite.
Ce mécanisme est exposé à travers la fonction dnew, qui est un
alias pour la méthode Hoa\Core\Consistency::dnew, pour des
raisons de facilité d'écriture et de lecture. Cette fonction utilise deux
paramètres : le nom de la classe et les arguments du constructeur rassemblés
dans un tableau. Ainsi :
$server = dnew('Hoa\Socket\Server', array($arguments));
La classe dynamiquement créée n'a pas besoin d'être pré-chargée, tout se fera en une seule fois.
Ce mécanisme a un intérêt plus important lorsque nous considèrons plusieurs familles potentielles contenant notre classe. Ainsi :
$server = dnew('(Hoathis or Hoa)\Socket\Server', array($arguments));
var_dump(get_class($server));
// Will output:
// string(32) "Hoathis\Socket\Server"
// or:
// string(28) "Hoa\Socket\Server"
Ce mécanisme est très intéressant lorsque nous proposons une implémentation
par défaut que l'utilisateur peut remplacer par sa propre implémentation dans
une autre famille. C'est un avantage pour ajouter de la modularité dans son
programme. Toutefois, il faudra porter une attention toute particulière au
type des données manipulées en vérifiant par exemple les interfaces, ceci
n'étant pas effectué par dnew.
Exercice : notre première classe
Nous allons commencer par un exercice très facile : créer une classe dans
la famille Hoathis, l'importer et l'utiliser.
Par défaut, les bibliothèques utilisateurs se placent entre autres dans
le dossier Module/ de Hoa (situé normalement dans
/usr/local/lib/hoa). Nous allons y créer le fichier
Exercise/Core/First.php :
<?php
namespace Hoathis\Exercise\Core {
class First {
public function say ( ) {
echo 'Hello world!', "\n";
}
}
}
Nous notons que l'espace de nom suit le chemin vers le fichier. Nous allons maintenant utiliser cette classe dans n'importe quel fichier, n'importe où :
<?php
require '/usr/local/lib/hoa/Core/Core.php';
from('Hoathis')
-> import('Exercise.Core.First');
$first = new Hoathis\Exercise\Core\First();
$first->say();
// Will output:
// Hello world!
Bravo ! Vous venez de créer votre première bibliothèque
Hoathis !
Callable
La couche Consistency propose un autre objet qui est
Hoa\Core\Consistency\Xcallable et qui permet d'étendre le
principe des callbacks aux
fonctionnalités proposées par Hoa.
Il existe la fonction alias xcallable qui permet de
construire un objet Hoa\Core\Consistency\Xcallable. Cette
fonction utilise deux arguments qui sont systématiques
dans Hoa : $call et $able, le dernier étant
optionnel. Nous pouvons les utiliser de la manière suivante :
xcallable('function');xcallable('class::method');xcallable('class', 'method');xcallable($object, 'method');xcallable($object);xcallable(function ( … ) { … }).
Nous remarquons que beaucoup de formes sont supportées. Dans tous les cas, pour effectuer un appel, nous ferons comme ceci :
$callable = xcallable(…);
$callable($argument1, $argument2, …);
Si nous donnons un objet mais que la méthode n'est pas précisée, c'est
Hoa\Core\Consistency\Xcallable qui va déterminer la méthode :
si c'est un événement, si c'est un flux, si c'est une exception etc.
Nous noterons deux méthodes qui peuvent avoir leur utilité :
getValidCallback qui retournera un callback avec un
format PHP valide et getHash qui retournera un identifiant
unique pour cet appel.
Il est préférable de savoir réagir lorsque nous rencontrons le couple
d'arguments $call et $able dans une méthode. Nous
aurons alors le réflexe de fournir des données comme présentées ci-dessus. Un
exemple se trouve dans la section suivante avec les événements.
Event
Une des fonctionnalités que Hoa ajoute à PHP est la gestion des événements. Les événements sont très pratiques pour faire interagir des composants entre eux. Nous distinguons alors deux catégories d'événements :
- événements : asynchrones à l'enregistrement, anonymes à l'utilisation et utiles pour une large diffusion de données à travers des composants qui n'ont aucune connexion entre eux ;
- écouteurs : a contrario des événements, synchrones à l'enregistrement, identifiés à l'utilisation et utiles pour des interactions proches entre un ou quelques composants.
Ces définitions peuvent laisser perplexe au début mais nous allons détailler les cas d'utilisations.
Créer et envoyer des événements
Si une classe veut être capable d'émettre des événements, elle doit
obligatoirement implémenter l'interface
Hoa\Core\Event\Source, les données qui transitent dans un
canal d'événements sont contenues dans la classe
Hoa\Core\Event\Bucket et l'enregistrement d'un événement ne
s'effectue qu'une seule fois et est associé à une classe. Autrement dit :
nous associons un objet à un canal d'événements et seul cet objet est
capable d'émettre sur ce canal. L'enregistrement s'effectue à l'aide de la
méthode Hoa\Core\Event::register, où le premier argument est
l'identifiant de l'événement et le second argument est le propriétaire (ou
l'émetteur) :
Hoa\Core\Event::register('id', $observable);
Une fois l'événement enregistré (ou créé), nous sommes capable d'émettre
grâce à la méthode Hoa\Core\Event::notify, dont le premier
argument est l'identifiant de l'événement, le deuxième est l'émetteur et le
dernier les données :
Hoa\Core\Event::notify('id', $observable, new \Hoa\Core\Event\Bucket($data));
Format des identifiants d'événements
Un événement porte un identifiant unique. Hoa a adopté un
formalisme pour nommer ses événements :
hoa://Event/Package[/anId[:pseudo-class]][#anotherId].
Quelques exemples d'événements connus dans Hoa :
hoa://Event/Exceptionpour écouter toutes les exceptions ;hoa://Event/Stream/stream-namepour écouter un flux;hoa://Event/Stream/stream-name:close-beforepour déclencher une action juste avant la fermeture d'un flux ;hoa://Event/Log/channelpour écouter des logs ;- etc.
Capturer les événements
La capture des événements se veut facilitée dans Hoa grâce à la fonction
event, alias de Hoa\Core\Event::getEvent. Cette
fonction requiert un seul argument : un identifiant, et va retourner
l'événement associé. Sur ce dernier, nous pourrons alors attacher au moyen de
la méthode attach des actions : une fonction déclarée, une
fonction anonyme, une classe et une méthode, un objet et une méthode, un flux
etc. Ainsi, si nous voulons capturer des exceptions :
event('hoa://Event/Exception')->attach(
function ( Hoa\Core\Event\Bucket $bucket ) {
$exception = $bucket->getData();
echo '** Exception (', get_class($exception), ') **', "\n",
'** from ', get_class($bucket->getSource()), ' **', "\n",
$exception->getFormattedMessage(), "\n\n";
}
);
Même si les exceptions sont capturées, elles sont envoyées sur le canal
d'événement. De cette façon, nous sommes capables de journaliser toutes les
exceptions en « redigirant » l'événement vers un flux, par exemple un fichier
Exception.log :
from('Hoa')
-> import('File.Write');
event('hoa://Event/Exception')->attach(new Hoa\File\Write('Exception.log'));
try {
throw new Hoa\Core\Exception('I\'m an error!', 0);
}
catch ( Hoa\Core\Exception $e ) {
// Shuut.
}
Nous parlons d'asynchrone à l'enregistrement car même si
l'événement n'est pas encore créé, nous pourrons dans tous les cas y attacher
une action. Nous parlons d'anonymat à l'utilisation car nous
ne savons pas qui va nous envoyer l'événement (nous le saurons une fois
l'événement reçu grâce à la méthode getSource). Et nous parlons
de larges diffusions car n'importe quel composant peut
écouter n'importe quel canal d'événements.
Exercice : ajout d'un événement
Nous allons créer la classe Hoathis\Exercise\Core\Second pour
lui ajouter des événements :
<?php
namespace Hoathis\Exercise\Core {
class Second implements \Hoa\Core\Event\Source {
public function __construct ( ) {
\Hoa\Core\Event::register('hoa://Event/Exercise', $this);
}
public function doSomething ( $who ) {
\Hoa\Core\Event::notify(
'hoa://Event/Exercise',
$this,
new \Hoa\Core\Event\Bucket('Hello ' . $who)
);
return mt_rand(42, 73);
}
}
}
Et dans n'importe quel fichier n'importe où :
<?php
require '/usr/local/lib/hoa/Core/Core.php';
from('Hoathis')
-> import('Exercise.Core.Second');
event('hoa://Event/Exercise')->attach(function ( Hoa\Core\Event\Bucket $bucket ) {
echo 'I have received “', $bucket->getData(), '“.', "\n";
});
$second = new Hoathis\Exercise\Core\Second();
var_dump($second->doSomething('Gordon'));
// Will output:
// I have received “Hello Gordon”.
// int(53)
Plus d'intimité avec les écouteurs
Les écouteurs ont quelques différences avec les événements. Tout d'abord,
une classe qui propose des écouteurs doit implémenter l'interface
Hoa\Core\Event\Listenable (qui est un enfant de l'interface
Hoa\Core\Event\Source). Cette nouvelle interface nous impose
d'écrire la méthode on qui va permettre d'attacher une action à
un écouteur. Le mécanisme d'écouteurs est basé sur la classe
Hoa\Core\Event\Listener et peut être porté par la classe ; peu
importe comment du moment que nous proposons la méthode on. Le
constructeur de cette classe a deux arguments : le premier pour désigner le
propriétaire des écouteurs, le second est un tableau des écouteurs
disponibles. Enfin, pour lancer un écouteur, nous utiliserons la méthode
Hoa\Core\Event\Listener::fire. Les données des écouteurs sont
également portées par la classe Hoa\Core\Event\Bucket.
Nous créons la classe Hoathis\Exercise\Core\Third :
<?php
namespace Hoathis\Exercise\Core {
class Third implements \Hoa\Core\Event\Listenable {
protected $_on = null;
public function __construct ( ) {
$this->_on = new \Hoa\Core\Event\Listener(
$this,
array('foo', 'bar')
);
}
public function on ( $listenerId, $call, $able = '' ) {
return $this->_on->attach($listenerId, $call, $able);
}
public function doSomething ( $who ) {
$this->_on->fire('foo', new \Hoa\Core\Event\Bucket(
'Hello ' . $who
));
return mt_rand(42, 73);
}
}
}
Et dans n'importe quel fichier n'importe où :
<?php
require '/usr/local/lib/hoa/Core/Core.php';
from('Hoathis')
-> import('Exercise.Core.Third');
$third = new Hoathis\Exercise\Core\Third();
$third->on('foo', function ( Hoa\Core\Event\Bucket $bucket ) {
echo 'I have received “', $bucket->getData(), '“.', "\n";
});
var_dump($third->doSomething('Gordon'));
// Will output:
// I have received “Hello Gordon”.
// int(53)
Nous parlons de synchrones à l'enregistrement car le composant qui porte les écouteurs existe nécessairement pour y attacher des actions. Nous parlons d'identification à l'utilisation car nous connaissons le composant que nous écoutons. Et c'est pourquoi nous parlons d'interactions proches car il est obligatoire d'avoir une proximité pour pouvoir interagir avec ce composant, d'où le fait que ça ne concerne que quelques composants.
Exception
Dans Hoa, il existe trois catégories d'exceptions (dans l'ordre de parenté) :
Hoa\Core\Exception\Idle, standard, parente de toutes les exceptions ;Hoa\Core\Exception, standard sauf qu'elle se copie sur un canal d'événements à la fin de sa construction ;Hoa\Core\Exception\Error, est l'équivalent des erreurs PHP.
Toutes les exceptions des bibliothèques de Hoa étendent
Hoa\Core\Exception, ce qui implique que toutes les exceptions
sont copiées sur le canal d'événements dédié, à savoir
hoa://Event/Exception.
Toutes les erreurs PHP sont également converties en exception
Hoa\Core\Exception\Error (excepté les erreurs fatales qui
causent l'arrêt de l'exécution). Cela ajoute une certaine uniformité dans le
comportement de vos programmes. Et comme précisé, chaque exception présentée
est parente de la suivante. Par conséquent, les erreurs sont également
copiées sur le canal d'événements.
Construire une exception
Le constructeur des exceptions est simple et est constitué de quatre arguments : un message formaté, un code, une liste d'arguments pour le message formaté et une exception (si un lien de causalité existe, notion abordée rapidement). Seul le premier argument est obligatoire. Ainsi :
try {
throw new Hoa\Core\Exception('Hello Gordon.');
}
catch ( Hoa\Core\Exception $e ) {
echo $e->getMessage(), "\n",
$e->getFormattedMessage();
// Will output:
// Hello Gordon.
// Hello Gordon.
}
Avec un message formaté :
try {
throw new Hoa\Core\Exception('Hello %s.', 42, 'Gordon');
}
catch ( Hoa\Core\Exception $e ) {
echo $e->getMessage(), "\n",.
$e->getFormattedMessage();
// Will output:
// Hello %s.
// Hello Gordon.
}
Avec un message formaté avec plus d'arguments :
try {
throw new Hoa\Core\Exception('%s %s.', 42, array('Hello', 'Gordon'));
}
catch ( Hoa\Core\Exception $e ) {
echo $e->getMessage(), "\n",
$e->getFormattedMessage();
// Will output:
// %s %s.
// Hello Gordon.
}
Nous aurons compris qu'il faut préférer la méthode
getFormattedMessage à getMessage pour avoir un
message lisible dans tous les cas.
Exception non-capturée
Si une exception n'est pas capturée explicitement, elle le sera par Hoa à la fin de sa remontée. Un message résumant l'exception sera affiché sur le flux de sortie courant de PHP. Ainsi :
++$foo;
// Will output:
// Uncaught exception (Hoa\Core\Exception\Error):
// Hoa\Core\Exception\Idle::error(): (-1) Undefined variable: foo
// in /Flatland/Foobar.php at line 42.
Exception imbriquée
Parfois, il existe un lien de causalité entre les exceptions. En effet, nous ne pouvons pas toujours faire remonter les exceptions telles quelles pour des raisons de facilité d'utilisation. C'est pourquoi nous avons la possibilité d'imbriquer les exceptions entre elles. Ainsi :
try {
throw new Hoa\Core\Exception\Idle('I\'m a nested exception!', 42);
}
catch ( Hoa\Core\Exception\Idle $e ) {
throw new Hoa\Core\Exception(
'Oh, something wrong happened.', 53, null, $e);
}
// Will output:
// Uncaught exception (Hoa\Core\Exception\Exception):
// {main}: (53) Oh, something wrong happened.
// in /Flatland/Foobar.php at line 13.
//
// ⬇
//
// Nested exception (Hoa\Core\Exception\Idle):
// {main}: (42) I'm a nested exception!
// in /Flatland/Foobar.php at line 18.
Nous remarquons que l'affichage des exceptions non-capturées sait
repérer les imbrications. Pour cela, nous avons la méthode
getPreviousThrow qui retourne l'exception causale ou
null si aucune n'existe.
De même, nous remarquons que la première exception levée est de type
Hoa\Core\Exception\Idle. Ainsi, seule la seconde exception sera
copiée sur le canal d'événements approprié. Nous pourrons toutefois retrouver
la première en utilisant justement la méthode
getPreviousThrow.
Exercice : créer sa propre exception
Cet exercice est très facile mais utile : nous allons créer notre propre
exception à notre bibliothèque. Pour cela, nous créons le fichier
Exercise/Core/Exception.php :
<?php
namespace Hoathis\Exercise\Core {
class Exception extends \Hoa\Core\Exception { }
}
Et pour l'utiliser, par exemple dans
Hoathis\Exercise\Core\Fourth, rien de plus simple :
<?php
namespace {
from('Hoathis')
-> import('Exercise.Core.Exception');
}
namespace Hoathis\Exercise\Core {
class Fourth {
public function __construct ( ) {
throw new Exception('Bazinga!');
}
}
}
Et enfin, pour tester notre exception, dans n'importe quel fichier n'importe où :
<?php
require '/usr/local/lib/hoa/Core/Core.php';
from('Hoathis')
-> import('Exercise.Core.Fourth');
try {
$fourth = new Hoathis\Exercise\Core\Fourth();
}
catch ( Hoathis\Exercise\Core\Exception $e ) {
echo '** Exception **', "\n",
$e->getFormattedMessage();
}
// Will output:
// ** Exception **
// Bazinga!
Protocol
Le protocole hoa:// permet d'abstraire l'accès à des
ressources. Il existe trois racines principales que nous pouvons
regrouper en deux catégories.
Format du protocole
Le protocole adopte le formalisme suivant :
hoa://root[/component][#anchor].
Toutefois, ce formalisme n'est pas fermé. Il peut exister des différences et
des subtilités. Cela sera toujours précisé lorsque ce sera le cas.
Les composants qui seront présentés dans les sections suivantes sont ceux par défaut. Ils peuvent bien sûr être modifiés. De plus, nous avons la possibilité d'ajouter facilement des composants. Ainsi, nous pourrons créer nos propres abstractions.
Abstraction destinée aux applications
Deux racines sont destinées à l'application courante, i.e. exploitant Hoa :
hoa://Applicationpour accéder à l'application ;hoa://Datapour accéder aux données de l'application.
À partir de ces racines, chaque composant du protocole peut être vu comme un lien symbolique vers un dossier réel.
Pour la première racine, seul un composant existe par défaut :
hoa://Application/Public qui donne accès à toutes les données
publiques, i.e. toutes les données accessibles directement par
l'utilisateur ; par exemple : hoa://Application/Public/index.php.
Autre exemple, la classe Hoa\Xyl propose les composants
suivants :
hoa://Application/Public/theme/Css/pour accéder aux feuilles CSS ;hoa://Application/Public/theme/Javascript/pour accéder aux scripts Javascript ;- etc.
Pour la seconde racine, plusieurs composants existent par défaut :
hoa://Data/Etc/Configuration/pour les configurations ;hoa://Data/Etc/Locale/pour les données liées à la localisation ;hoa://Data/Lost+found/pour les ressources perdues ou trouvées (normalement toujours vide) ;hoa://Data/Module/pour les bibliothèques utilisateurs restreintes à l'application courante (on y reviendra) ;hoa://Data/Temporary/pour les données temporaires ;hoa://Data/Variable/pour les fichiers modifiés régulièrement (dits variables), comme les caches, les bases de données, les logs, les tests etc.
Nous les utilisons simplement de cette façon :
from('Hoa')
-> import('File.Write');
$file = new Hoa\File\Write('hoa://Data/Temporary/Foobar.txt');
$file->writeAll('Bazqux');
Cela fonctionne également sur des instructions bien plus élémentaires :
$foo = require 'hoa://Data/Etc/Configuration/.Cache/HoaCoreCore.php';
Nous verrons plus en détail comment cela fonctionne quand nous écrirons une application. Il faut retenir que c'est une suite de composants, chacun pouvant être vu comme un lien symbolique. L'utilisation de ce protocole dans votre programme abstrait totalement la dépendance aux ressources. Modifier le nom d'un dossier, voire carrément le déplacer, n'engendrera pas de modification du code. De la même manière, distribuer une bibliothèque (ou quelque chose de plus conséquent) dépendant de ressources pourra s'effectuer sans souci : le protocole sera interprété par l'application courante, avec ses propres configurations. La maintenance est alors considérablement réduite.
Abstraction destinée aux bibliothèques
Une racine est destinée à l'accès aux bibliothèques standards :
hoa://Library. Actuellement, nous rencontrons peu de cas
d'utilisations. La bibliothèque Hoa\Xyl propose des ressources
accessibles de cette manière (mais pas par l'utilisateur, uniquement par le
système interne, le protocole se transforme à la volée). Un exemple
intéressant est celui du registre statique Hoa\Registry qui place
sur le protocole une ressource qui n'est pas un fichier mais une donnée de PHP
(une données scalaire, un tableau, une fonction, un objet etc.), ainsi :
from('Hoa')
-> import('Registry.~');
Hoa\Registry::set('foo', 'bar');
var_dump(
resolve('hoa://Library/Registry#foo')
);
// Will output:
// string(3) "bar"
Nous pouvons imaginer stocker des instances de cache, de base de données, des fonctions anonymes etc.
Nous remarquerons l'utilisation de la fonction resolve, un
alias plus évolué, qui permet de résoudre des composants du protocole
hoa://.
