Hoa, un ensemble de bibliothèques PHP

Logo de l'AFUP Lyon

Qui sommes-nous ?

Ivan Enderlin :

ivan.enderlin@ { hoa-project.net atoum.org inria.fr

Qui sommes-nous ?

Julien Bianchi :

@jubianchi { twitter.com github.com
julien.bianchi@ { atoum.org hoa-project.net

Philosophie

Une bibliothèque est un ensemble de fonctionnalités dédiées à la résolution d'un problème précis et isolé, grâce à son abstraction la plus élevée
L'abstraction permet de s'adapter à plus de formes de problèmes

La Force est dans l'abstraction

Contributions

Hoa :

Agenda

  1. Présentation du projet
  2. Quelques fondamentaux
  3. Bibliothèques phares
  4. Au-delà de Hoa
  5. Conclusion

1 Présentation du projet

Projets distribués

Liste complète

Sources, e.g. Hoa\Core

Git Via Git, project.git :

$ git clone http://git.hoa-project.net/Library/Core.git

Github Via Github, sans Library/ :

$ git clone https://github.com/hoaproject/Core.git

Composer Via Composer, avec le schéma :

{ "require": { "hoa/core": "dev-master" }, "minimum-stability": "dev" }

… puis :

$ composer install

Via les archives sur download.hoa-project.net

Structure des bibliothèques

Rolling-release

Composer :

For every branch, a package “development version” will be created. [...] master results in a dev-master version.

Utiliser @dev dans vos fichiers composer.json

Quelques chiffres*

Nombre de :

* entre le 1 janvier 2013 et aujourd'hui

Litératures

Documentations :

Awecode : quand le code rencontre la vidéo

Recherche : 3 articles en conférences internationales

Autres événements communutaires, industriels (dont 2 par l'AFUP, merci !), formations etc.

(traduction en cours vers l'anglais)

Communauté

2 Quelques fondamentaux

Bibliothèque

Avantages :

Inconvénient :

Hoa\Core

Architecture générale :

Charge en < 5ms.

Protocole hoa://

Format : hoa://Root[/Component]*[#anchor]

Abstrait l'accès à des ressources :

Hoa\Registry::set('foo', 'bar');
var_dump(resolve('hoa://Library/Registry#foo')); // string(3) "bar"
require 'hoa://Data/Configuration/.Cache/Foo.php';
$ hoa compiler:pp hoa://Library/Json/Grammar.pp <( echo '[1, 2, 3]' ) -v dump
$ hoa core:resolve --tree hoa://

Événements & écouteurs

Événements : asynchrones (à l'enregistrement), anonymes (à l'utilisation), large diffusion à travers des composants isolés

event('hoa://Event/Exception')->attach(new Hoa\File\Write('Foo.log'));

Écouteurs : synchrones (à l'enregistrement), identifiés (à l'utilisation), interactions proches entre un ou quelques composants

$server = new Hoa\Websocket\Server(…);
$server->on('message', function ( Hoa\Core\Event\Bucket $bucket ) {

    $source = $bucket->getSource(); // instanceof Hoa\Core\Event\Source
    $data   = $bucket->getData();   // Array
});

Consistence

Dans et entre les versions de PHP et Hoa :

Flux uniformisés

Flux de PHP très puissants (!), uniformisés à travers plusieurs API et interfaces de Hoa\Stream :

Exemples : Hoa\File, Hoa\Socket, Hoa\Http

3 Bibliothèques phares

Hoa\Websocket

WebSocket, protocole réseau standardisé (RFC6455), full-duplex et bidirectionnel ; communication via TCP ; handshake via HTTP :

Hoa\Websocket propose un client et un serveur :

Un message WebSocket*

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+

* 5.2. Base Framing Protocol

Un serveur WebSocket

Flux : Hoa\Socket\Server écoute 127.0.0.1:8889

Écouteurs : open, message, binary-message, ping, error et close

Exemple d'un écho :

$server = new Hoa\Websocket\Server(
    new Hoa\Socket\Server('tcp://127.0.01:8889')
);
$server->on('message', function ( Hoa\Core\Event\Bucket $bucket ) {

    $message = $bucket->getData()['message'];

    echo 'Message: ', $message, "\n";
    $bucket->getSource()->send($message);

    return;
});
$server->run();

Un client WebSocket (.html)

<input type="text" id="input" placeholder="Message…" />
<pre id="output"></pre>

<script>
  var host   = 'ws://127.0.0.1:8889';
  var socket = null;
  var input  = document.getElementById('input');
  var output = document.getElementById('output');
  var print  = function ( message ) {

      var samp       = document.createElement('samp');
      samp.innerHTML = message + '\n';
      output.appendChild(samp);

      return;
  };

  input.addEventListener('keyup', function ( evt ) {

      if(13 === evt.keyCode) {

          var msg = input.value;

          if(!msg)
              return;

          try {

              socket.send(msg);
              input.value = '';
              input.focus();
          }
          catch ( e ) {

              console.log(e);
          }

          return;
      }
  });

  try {

      socket = new WebSocket(host);
      socket.onopen = function ( ) {

          print('connection is opened');
          input.focus();

          return;
      };
      socket.onmessage = function ( msg ) {

          print(msg.data);

          return;
      };
      socket.onclose = function ( ) {

          print('connection is closed');

          return;
      };
  }
  catch ( e ) {

      console.log(e);
  }
</script>

      

Un serveur WebSocket

Exemple d'un écho de broadcast :

$server = new Hoa\Websocket\Server(
    new Hoa\Socket\Server('tcp://127.0.01:8889')
);
$server->on('message', function ( Hoa\Core\Event\Bucket $bucket ) {

    $message = $bucket->getData()['message'];

    echo 'Message: ', $message, "\n";
    $bucket->getSource()->broadcast($message);

    return;
});
$server->run();

Un client WebSocket (.php)

Flux : Hoa\Socket\Client écoute 127.0.0.1:8889

Écouteurs : les mêmes

Exemple en CLI d'un prompt qui envoie des messages :

$readline = new Hoa\Console\Readline();
$client   = new Hoa\Websocket\Client(
    new Hoa\Socket\Client('tcp://127.0.0.1:8889')
);
$client->setHost('localhost');
$client->connect();

do {

    $line = $readline->readLine('> ');

    if(false === $line || 'quit' === $line)
        break;

    $client->send($line);

} while(true);

Hoa\Console

Basée sur le standard ECMA-48 et des API très proches de Linux, FreeBSD ou System V

Jouer avec le curseur

use Hoa\Console\Cursor;

Les couleurs :

Cursor::colorize('underlined foreground(yellow) background(#932e2e)');
echo 'foo';
Cursor::colorize('!underlined background(normal)');
echo 'bar';

Les déplacements :

Cursor::save();
Cursor::hide();
Cursor::move('← ← ← ↓ ↓');
Cursor::clear('↔');
echo 'New line!';
Cursor::restore();
Cursor::show();

Mais aussi : getPosition, moveTo, setStyle etc.

Jouer avec la fenêtre

use Hoa\Console\Window;

La taille et la position :

Window::setSize(80, 50);
var_dump(Window::getPosition());

L'état :

Window::minimize();
Window::restore();
Window::lower();
Window::raise();

Le contenu :

Window::scroll('↑ ↑ ↑');
Window::refresh();

Mais aussi : setTitle, getTitle, getLabel, copy etc.

Lecture avancée de ligne

Opérations supportées : édition avancée, historique, auto-complétion et UTF-8 :

Auto-complétion

Hoa\Console\Readline\Autocompletion\Word avec la liste des fonctions PHP :

$readline = new Hoa\Console\Readline();
$readline->setAutocompleter(new Hoa\Console\Readline\Autocompleter\Word(
    get_defined_functions()['internal']
));

do {

    $line = $readline->readLine('> ');

    if(false === $line || 'quit' === $line)
        break;

    echo $line, "\n";

} while(true);

Fonctionne avec un pipe ou une redirection !

Ajouter un raccourci

Inverser la casse de la ligne avec Ctrl + R :

$readline->addMapping('\C-R', function ( Hoa\Console\Readline $self ) {

    // Clear the line.
    Hoa\Console\Cursor::clear('↔');
    echo $self->getPrefix();

    // Get the line text.
    $line = $self->getLine();

    // New line.
    $new  = null;

    // Loop over all characters.
    for($i = 0, $max = $self->getLineLength(); $i < $max; ++$i) {

        $char = mb_substr($line, $i, 1);

        if($char === $lower = mb_strtolower($char))
            $new .= mb_strtoupper($char);
        else
            $new .= $lower;
    }

    // Set the new line.
    $self->setLine($new);

    // Set the buffer (and let the readline echoes or not).
    $self->setBuffer($new);

    // The readline will continue to read.
    return $self::STATE_CONTINUE;
});

Processus

Exécution rapide :

var_dump(Hoa\Console\Processus::execute('id -u -n'));

Exécution « dynamique », écouteurs start, stop, input, output et timeout :

$processus = new Hoa\Console\Processus($command);
$processus->on('input', function ( $bucket ) {

    $bucket->getSource()->writeAll(…);

    return false; // means to close the pipe.
});
$processus->on('output', function ( $bucket ) {

    echo $bucket->getData()['line'], "\n";
});
$processus->run();

(Hoa or Symfony)\Console

Symfony :

Hoa :

Hoa\Compiler

Analyser et manipuler des données textuelles

Hoa\Compiler\Llk est un compilateur de compilateurs :

Grammaire écrite en langage PP (langage maison)

Grammaire simplifiée de JSON

Lexèmes (unité lexicale, atomique) :

%skip   space          \s
// Scalars.
%token  true           true
%token  false          false
%token  null           null
// Strings.
%token  quote_         "        -> string
%token  string:string  [^"]+
%token  string:_quote  "        -> default
// Objects.
%token  brace_         {
%token _brace          }
// Arrays.
%token  bracket_       \[
%token _bracket        \]
// Rest.
%token  colon          :
%token  comma          ,
%token  number         \d+

Règles :

value:
    <true> | <false> | <null> | string() | object() | array() | number()

string:
    ::quote_:: <string> ::_quote::

number:
    <number>

#object:
    ::brace_:: pair() ( ::comma:: pair() )* ::_brace::

#pair:
    string() ::colon:: value()

#array:
    ::bracket_:: value() ( ::comma:: value() )* ::_bracket::

Analyser du JSON

Manuellement :

// 1. Load grammar.
$compiler = Hoa\Compiler\Llk::load(new Hoa\File\Read('Json.pp'));

// 2. Parse a data.
$ast      = $compiler->parse('{"foo": true, "bar": [null, 42]}');

// 3. Dump the AST.
$dump     = new Hoa\Compiler\Visitor\Dump();
echo $dump->visit($ast);

/**
 * Will output:
 *     >  #object
 *     >  >  #pair
 *     >  >  >  token(string:string, foo)
 *     >  >  >  token(true, true)
 *     >  >  #pair
 *     >  >  >  token(string:string, bar)
 *     >  >  >  #array
 *     >  >  >  >  token(null, null)
 *     >  >  >  >  token(number, 42)
 */

En ligne de commande :

$ hoa compiler:pp Json.pp <( echo '{"foo": true, "bar": [null, 42]}' ) -v dump

Finaliser la compilation

Utiliser Hoa\Visitor pour visiter l'arbre

Opérations possibles :

* « langage interprété »

** « langage compilé »

Génération de données

Grammar-based data generation :

Exemple avec la grammaire des… PCRE (voir Hoa\Regex)

$ hoa praspel:shell
// …
> regex: /fo{2,4}ba[rz]+/
> .sample regex
'foobazzz'
> .sample regex
'foooobazrzrzrzz'

Hoa\Ruler

Bel exemple d'utilisation de Hoa\Compiler

Moteur de règles, écrites dans un langage dédié, proche d'SQL

$ruler = new Hoa\Ruler();

// 1. Write a rule.
$rule  = 'group in ("customer", "guest") and points > 30';

// 2. Create a context.
$context           = new Hoa\Ruler\Context();
$context['group']  = 'customer';
$context['points'] = function ( ) {

    return 42;
};

// 3. Assert!
var_dump(
    $ruler->assert($rule, $context)
);

Fonctionnement

Exemple de compilation vers PHP :

$database->save(
    serialize(
        Hoa\Ruler::interprete(
            'group in ("customer", "guest") and points > 30'
        ) // rule → AST → interpreter → model
    ) // model → serialized
);

Praspel

PHP Realistic Annotation and SPEcification Language

Programmation par contrat

Décrit un modèle en utilisant des annotations

Contraintes formelles : pré-, postconditions et invariants

Inclue dans les commentaires /** … */ du code source au niveau des données : attributs et méthodes

Sujet de thèse, effectuée au DISC et à l'INRIA

3 articles scientifiques publiés et 1 journal en préparation

Unir plusieurs théories au sein d'un langage simple et efficace

Exemple basique

class C {

    /**
     * @invariant foo: float();
     */
    protected $foo = 0;

    /**
     * @requires x      : integer() or string('a', 'z', 1);
     * @ensures  \result: boolean();
     */
    public function f ( $x ) { … }
}

Que faire d'un contrat ?

Design-by-Contract :

Contract-based testing :

Qu'est-ce qu'un test ?

Suite de test : ensemble de cas de tests

* Amusons-nous avec hoa praspel:shell !

** Amusons-nous avec… hoa hoathis atoum:generate !

4 Au-delà de Hoa

Symfony

Intégration très simple :

Plusieurs bundles disponibles :

Une question d'API

Un bundle contient au moins :

Adapter l'API

Tout est dans la bibliothèque

Juste implémenter les interfaces de Symfony :

use Hoa\Stringbuffer\ReadWrite;
use Hoa\Xml\Exception\Exception as XmlException;
use Hoa\Xyl\Interpreter\Html\Html;
use Hoa\Xyl\Xyl;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\Templating\TemplateNameParserInterface;

class Engine implements EngineInterface
{
    public function render($name, array $parameters = array())
    {
        if (false === $this->exists($name)) {
            throw new \InvalidArgumentException(…);
        }

        try {
            $xyl = new Xyl($this->load($name), new ReadWrite(), new Html());
        } catch (XmlException $exception) {
            throw new \RuntimeException(…);
        }

        try {
            $xyl->interprete();
            $xyl->render();
        } catch(\Exception $exception) {
            throw new \RuntimeException(…);
        }

        return $xyl->getOutputStream()->readAll();
    }
}

Hoathis\BenchBundle

Bundle basé sur la bibliothèque Hoa\Bench

Totalement intégré au profiler et à la console

Installation :

{ "require-dev": { "hoathis/bench-bundle": "dev-master" } }

Démarrage (app/AppKernel.php) :

if (in_array($this->getEnvironment(), array('dev', 'test'))) {
    // …
    $bundles[] = new \Hoathis\Bundle\BenchBundle\BenchBundle();
}

Voir github.com/jubianchi/HoathisBenchBundle

Hoathis\BenchBundle

Service bench dans les controlleurs :

class FooController extends Controller
{
    public function barAction()
    {
        $this->container->get('bench')->foo->start();
        //…
        $this->container->get('bench')->foo->stop();

        return new Response();
    }
}

Helper Twig :

{% benchstart 'listing' %}

{% for item in items %} … {% endfor %}

{% benchstop 'listing' %}

Sohoa

Hoa est un ensemble de bibliothèques

Sohoa est un framework construit avec Hoa :

Voir github.com/sohoa

5 Conclusion

Contributions

Hoa :

Hackability!

Merci ! ☺︎

hoa-project.net
@hoaproject