Close search
Hoa

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

  1. Introduction
  2. Carte du noyau
  3. Consistency
    1. Chemin d'importation
    2. Plusieurs chemins pour une famille
    3. Pré-chargement, chargement et auto-chargement
    4. Importer depuis plusieurs familles
    5. Dynamic new
    6. Exercice : notre première classe
    7. Callable
  4. Event
    1. Créer et envoyer des événements
    2. Format des identifiants d'événements
    3. Capturer les événements
    4. Exercice : ajout d'un événement
    5. Plus d'intimité avec les écouteurs
  5. Exception
    1. Construire une exception
    2. Exception non-capturée
    3. Exception imbriquée
    4. Exercice : créer sa propre exception
  6. Protocol
    1. Format du protocole
    2. Abstraction destinée aux bibliothèques
    3. Abstraction destinée aux applications
  7. Script
    1. Plus de confort
    2. Premier pas avec le script hoa
    3. Créer notre propre commande
  8. Conclusion

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 :

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) :

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 :

  1. nous avons besoin d'une classe qui n'est pas chargée ;
  2. si elle n'est pas pré-chargée, une exception est levée ;
  3. 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 :

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 :

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 :

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é) :

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 :

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 :

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 :

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é :

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.

menu