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_once '/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 (ou
vendor) 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) :
Hoa
pour les bibliothèques standards de Hoa ;Hoathis
pour les bibliothèques utilisateurs (c'est une famille comme les autres sauf qu'elle a un privilège supplémentaire, nous le détaillerons plus tard).
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
Hoa/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
Hoa/
. 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 Hoa/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.
Plusieurs chemins pour une famille
Il est possible de spécifier plusieurs emplacements où
trouver une famille de bibliothèques. Par exemple, les familles
Hoa
et Hoathis
ont deux dossiers par défaut (nous le
verrons plus tard). Ceci a pour intérêt de pouvoir surcharger n'importe
quelle classe dans le code par notre propre classe, ou alors d'en ajouter de
nouvelles. Ce mécanisme de choix rend Hoa hautement modulaire et
extensible.
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"
*/
Encore une fois, ce mécanisme ajoute de l'extensibilité dans Hoa. De plus, ce mécanisme s'avère 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.
Les bibliothèques utilisateurs se placent dans le dossier
Hoathis/
, situé par défaut au même niveau que Hoa
.
Donc, nous allons créer le fichier
/usr/local/lib/Hoathis/Exercise/First.php
:
<?php
namespace Hoathis\Exercise {
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_once '/usr/local/lib/Hoa/Core/Core.php';
from('Hoathis')
-> import('Exercise.First');
$first = new Hoathis\Exercise\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]][#anAnchor]
.
Quelques exemples d'événements connus dans Hoa :
hoa://Event/Exception
pour écouter toutes les exceptions ;hoa://Event/Stream/stream-name
pour écouter un flux;hoa://Event/Stream/stream-name:close-before
pour déclencher une action juste avant la fermeture d'un flux ;hoa://Event/Log/channel
pour é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\Second
pour
lui ajouter des événements :
<?php
namespace Hoathis\Exercise {
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_once '/usr/local/lib/Hoa/Core/Core.php';
from('Hoathis')
-> import('Exercise.Second');
event('hoa://Event/Exercise')->attach(function ( Hoa\Core\Event\Bucket $bucket ) {
echo 'I have received “', $bucket->getData(), '“.', "\n";
});
$second = new Hoathis\Exercise\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\Third
:
<?php
namespace Hoathis\Exercise {
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_once '/usr/local/lib/Hoa/Core/Core.php';
from('Hoathis')
-> import('Exercise.Third');
$third = new Hoathis\Exercise\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/Exception.php
:
<?php
namespace Hoathis\Exercise {
class Exception extends \Hoa\Core\Exception { }
}
Et pour l'utiliser, par exemple dans
Hoathis\Exercise\Fourth
, rien de plus simple :
<?php
namespace {
from('Hoathis')
-> import('Exercise.Exception');
}
namespace Hoathis\Exercise {
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_once '/usr/local/lib/Hoa/Core/Core.php';
from('Hoathis')
-> import('Exercise.Fourth');
try {
$fourth = new Hoathis\Exercise\Fourth();
}
catch ( Hoathis\Exercise\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 bibliothèques
Une racine est destinée à l'accès aux bibliothèques
standards : hoa://Library
. Chaque ressource des
bibliothèques est accessible sur
hoa://Library/Libraryname/
. Par exemple,
hoa://Library/Json/Grammar.pp
permet d'accéder à la grammaire du
langage JSON située dans le fichier
Hoa/Json/Grammar.pp
. Un autre exemple 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\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://
.
En réalité, la racine hoa://Library/
pointe vers quatre
emplacements différent. Si la ressource est un fichier ou dossier, elle va
chercher au niveau de l'installation locale (si nous sommes dans une
application) et de l'installation globale, et ce, pour les deux familles
Hoathis
et Hoa
(dans cet ordre). C'est pour ça que
Hoathis
est une famille avec un privilège : elle est intégrée au
protocole hoa://
. Ce privilège peut être étendu à d'autres
familles, nous le verrons plus tard.
Abstraction destinée aux applications
Deux racines sont destinées à l'application courante :
hoa://Application
pour accéder à l'application ;hoa://Data
pour accéder aux données de l'application.
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
.
Pour la seconde racine, plusieurs composants existent par défaut :
hoa://Data/Etc/
pour les configurations, les données liées à la localisation etc. ;hoa://Data/Library/
, similaire àhoa://Library/
sauf que la recherche s'arrête à l'installation locale (à l'application) ;hoa://Data/Lost+found/
pour les ressources perdues ou trouvées (normalement toujours vide) ;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.
Script
La bibliothèque Hoa\Core
propose également un script en ligne
de commande : hoa
et hoa.bat
pour Windows. Pour
l'utiliser, il faut installer les bibliothèques Hoa\Router
,
Hoa\Dispatcher
, Hoa\Console
et
Hoa\String
. Ces scripts se trouvent dans le dossier
Hoa/Core/Bin/
. Lorsque nous exécutions un de ces scripts, un
écran d'accueil apparaît :
$ Hoa/Core/Bin/hoa
// Homescreen.
Plus de confort
Il sera plus confortable de placer les scripts hoa
dans le
$PATH
afin qu'ils soient accessibles depuis n'importe où sur le
disque. Pour cela, sous Unix, il faudra modifier la variable
$PATH
dans ~/.shellrc
(par exemple :
~/.bashrc
, ~/.zshrc
etc.) :
export PATH=/usr/local/lib/Hoa/Core/Bin:$PATH
Nous rechargeons notre profil et le script sera disponible partout :
$ source ~/.zshrc
$ hoa
// Homescreen.
Pour Windows, il faudra consulter Microsoft TechNet car la manipulation est moins triviale.
Premier pas avec le
script hoa
Tout d'abord, à n'importe quel moment, vous pouvez utiliser les options
-h
, --help
ou -?
pour obtenir de
l'aide.
Ensuite, le script fonctionne de la manière suivante : hoa
[vendor] library[:command] [tail]
.
Quelques explications :
vendor
représente la famille de bibliothèque et vauthoa
par défaut ;library
représente la bibliothèque qui va contenir les commandes et vautcore
par défaut ;command
représente la commande à exécuter et vautwelcome
par défaut ;tail
représente des options, des entrées etc.
En réalité, hoa
va exécuter la classe
Vendor\Library\Bin\Command
. Nous
remarquons que la seule partie fixe est Bin\
, c'est un espace de
nom particulier qui est compris par le script hoa
comme contenant
des commandes. Ainsi, quand nous exécutons :
$ hoa core:resolve hoa://Library --no-verbose
Cela va exécuter la classe Hoa\Core\Bin\Resolve
située sans
surprise dans le fichier Hoa/Core/Bin/Resolve.php
.
Créer notre propre commande
Nous allons créer notre propre commande rapidement, sans trop insister sur
les détails, mais uniquement pour donner un aperçu du fonctionnement. Créeons
le fichier Hoathis/Exercise/Bin/Hello
:
<?php
namespace Hoathis\Exercise\Bin {
class Hello extends \Hoa\Console\Dispatcher\Kit {
protected $options = array(
array('who', \Hoa\Console\GetOption::REQUIRED_ARGUMENT, 'w'),
array('help', \Hoa\Console\GetOption::NO_ARGUMENT, 'h'),
array('help', \Hoa\Console\GetOption::NO_ARGUMENT, '?')
);
public function main ( ) {
$who = 'world';
while(false !== $c = $this->getOption($v)) switch($c) {
case 'w':
$who = $v;
break;
case 'h':
case '?':
return $this->usage();
break;
case '__ambiguous':
$this->resolveOptionAmbiguity($v);
break;
}
echo 'Hello ', $who, '!', "\n";
}
public function usage ( ) {
echo 'Usage : exercise:hello <options>', "\n",
'Options :', "\n",
$this->makeUsageOptionsList(array(
'w' => 'Hello to who?',
'help' => 'This help.'
)), "\n";
}
}
}
Nous avons défini la commande exercise:hello
, avec deux
options (et leurs équivalents courts) : who
et help
.
Et maintenant testons !
$ hoa hoathis exercise:hello
Hello world!
$ hoa hoathis exercise:hello --who Gordon
Hello Gordon!
$ hoa hoathis exercise:hello --help
Usage : exercise:hello <options>
Options :
-w, --who= : Hello to who?
-h, --help : This help.
-?, --help : This help.
Amusez-vous à utiliser l'option --woo
au lieu de
--who
pour vous amuser.
Nous pouvons même créer un alias pour utiliser nos bibliothèques
Hoathis
:
$ alias hoathis="hoa hoathis"
$ hoathis exercise:hello
Hello world!
N'importe quelle famille de bibliothèques peut ainsi créer ses propres scripts.
Conclusion
Hoa\Core
est le cœur de Hoa et nous comprenons mieux pourquoi.
Cette bibliothèque à elle seule apporte déjà de grands services.
Hoa est un ensemble de bibliothèques modulaire, extensible et structuré :
- modulaire car chaque bibliothèque est bien isolée mais nous pouvons les assembler comme bon nous semble, les installer à plusieurs endroits de plusieurs manières ;
- extensible car nous pouvons pratiquement tout modifier, tout surcharger, et écrire nos propres bibliothèques (après tout, Hoa est un environnement de développement de bibliothèques et l'intégration des scripts est un service parmi d'autres) ;
- structuré car Hoa repose sur des logiques simples qui permettent cette modularité et cette extensibilité.
Nous verrons plus en détail dans les prochains chapitres pourquoi Hoa est réellement structuré : les bibliothèques ne sont pas écrites par hasard et elles offrent beaucoup de possibilités.