PHP sous stéroïde avec Zephir, est-ce une bonne idée ?

Vous en avez sûrement déjà entendu parlé : Zephir est un nouveau langage de programmation très proche de PHP et de C et dont l’objectif est de permettre la création d’extensions PHP sans connaissance de Zend Engine, ni de C. Les responsables du projet savent de quoi ils parlent, ils sont les auteurs du framework PhalconPHP, justement distribué sous forme d’extension1.

J’ai eu l’opportunité de donner une conférence sur Zephir à Be-Zend 2014 et je vous livre ici mon retour après quelques semaines d’utilisation.

Une extension PHP, mais pour quoi faire ?

Dans l’immense majorité des cas, PHP n’est pas le coupable si votre site est lent. Une réécriture en C ne corrigera pas vos problèmes s’ils sont causés par des I/O lents ou par un algorithme inefficace. Cela étant dit, on le sait tous que PHP a ses défauts et il n’est notamment pas très économe en mémoire. Ce qui est tout à fait normal pour un langage à typage faible.

Alors que Facebook fait un super boulot avec HHVM et Hack à la fois sur la vitesse d’exécution et la consommation de mémoire, 98% des applications PHP tournent sur Zend Engine ; et produire du code en Hack n’est donc pas la meilleure façon de s’assurer une compatibilité avec un maximum d’utilisateurs potentiels.

Écrire une extension PHP a plusieurs avantages :

  • améliorer sensiblement les performances et la consommation en mémoire d’opérations lourdes ;
  • obfusquer son code source, et ainsi distribuer des produits propriétaires pour PHP 2 ;
  • accéder à des API systèmes non exposées dans le userland PHP ;
  • s’assurer une compatibilité avec l’ensemble de l’écosystème existant ;
  • mettre des gifs animés dans le phpinfo() – j’ai vérifié, c’est possible !
  • devenir une star sur http://pecl.php.net/ :D

Vient ensuite le choix de Zephir face à une extension en C natif. Plus que le langage, qui est déjà une barrière importante pour un développeur du monde PHP (la gestion de la mémoire, le typage…), c’est tout le Zend Engine et ses complications qui sont cachés au développeur grâce a Zephir (à ce propos, have you met PHPCPP ?). Vous allez pouvoir produire une extension en ne faisant que du développement haut niveau, sans avoir à vous soucier des hooks internes de Zend Engine, de la mémoire et sans apprendre de nouvelle API.

Les pré-requis

Pour commencer, il vous faudra bien entendu tous les outils nécessaires à la compilation de PHP :

  • php5-dev ;
  • phpize ;
  • mais aussi libpcre3, gcc, make et json-c.

L’installation de Zephir consiste à le cloner en local et à lancer une commande d’installation (qui le copie dans votre /usr/local/bin) :

$ ./install -c

La commande zephir est ainsi disponible dans votre shell et va vous permettre de nombreuses opérations (construire votre extension, l’installer, créer un nouveau projet…).

Le formattage de Zephir étant différent de celui de PHP, votre éditeur sera perdu, je vous conseille donc ce plugin Sublime Text (2 et 3) qui prendra en charge la coloration syntaxique.

HelloWorld.so

Commençons par créer le squelette de notre extension avec la commande fournie :

zephir init jolicoucou
cd jolicoucou

Notre code Zephir se place dans jolicoucou/jolicoucou, vous remarquerez un repertoire jolicoucou/ext avec des fichiers .h et .c dont le compilateur va avoir besoin pour produire l’extension.

Nos classes vivent dans des fichiers .zep (et non .php) et possèdent toujours leur propre fichier (jamais deux classes dans un fichier .zep). Créons une classe nommée coucou.zep :

namespace Jolicoucou;

class Coucou
{
    public static function say(string! name) -> void
    {
        echo sprintf("Coucou %s !", name);
    }
}

Nous pouvons ensuite la compiler et l’installer très simplement :

$ zephir build

Preparing for PHP compilation...
Preparing configuration file...
Compiling...
Installing...
Extension installed!
Add extension=jolicoucou.so to your php.ini
Don't forget to restart your web server

jolicoucou.so devient alors une nouvelle extension du système, nous pouvons ajouter extension=jolicoucou.so dans notre fichier php.ini et notre classe Jolicoucou\Coucou sera disponible dans notre environnement PHP !

php -r 'echo Jolicoucou\Coucou::say("le monde")."\n";'
Coucou le monde !

Il s’agit de l’extension la plus nulle du monde mais vous voyez le principe ! Vous pourrez remarquer que la signature de ma fonction est fortement typée : string! devant le paramètre name n’acceptera qu’une chaîne de caractères (une belle Exception est levée dans le cas contraire), et -> void s’assure que rien ne sera retourné (le compilateur sortira un warning si ce n’est pas le cas).

Zephir possède quelques différences et limitations pour les développeurs PHP :

  • pas de <?php au début des fichiers ;
  • le dollar devant les variables n’est plus obligatoire (mais pas interdit) ;
  • pas de pointeurs ;
  • chaque modification d’une variable nécessite le keyword let pour fonctionner (cela donne des indications au compilateur) ;
  • toutes les variables d’une méthode doivent être déclarées et typées (un simple var étant le type dynamique de PHP) ;
  • tous les fichiers et dossiers doivent être en lowercase (c’est un peu WTF) ;
  • toutes les classes doivent être dans un namespace, correspondant à l’arborescence sur le système de fichier (à la différence près de la casse) ;
  • vous devez faire attention à la valeur maximum de vos integer et à quelques autres subtilités liées au typage ;
  • les globales de PHP ne sont pas supportées mais il est possible d’accèder aux Super Globales ;
  • le type « hinting » se fait avec des crochets (function test(<Coucou\Test> foo)) ;
  • et… il faut compiler pour tester ! (certainement le point le plus rebutant pour un développeur PHP je vous assure).

On ne peut plus utiliser foreach, mais for in :

// PHP
foreach ($this->listeners[$eventName] as $key => $foo) {
}

// Zephir
var $key, $foo;

for $key, $foo in $this->listeners[$eventName] {
}

Vous noterez qu’il faut déclarer explicitement les variables et qu’il n’y a pas de parenthèses. Il en est de même pour list() et elseif dont vous devrez vous passer :

// PHP
list($serviceId, $method, $priority) = $args;

// Zephir
var $priority, $method, $serviceId;
let $serviceId = $args[0];
let $method    = $args[1];
let $priority  = $args[2];

// PHP
if ($foo) { }
elseif ($bar) { }

// Zephir
if ($foo) { }
else {
    if ($bar) { }
}

Les assignations complexes avec des tableaux doivent être découpées :

// PHP
$this->listenerIds[$eventName][] = array($callback[0], $callback[1], $priority);

// Zephir
var $listener_service;
let $listener_service = $this->listenerIds[$eventName];
let $listener_service[] = [$callback[0], $callback[1], $priority];
let $this->listenerIds[$eventName] = $listener_service;

Et il n’est pas possible d’assigner des tableaux multi-dimensionnels à la volée :

// Zephir
let $list["foo"]["bar"] = 1337; // Produit une erreur si $list["foo"] n'existe pas

Le langage est encore jeune et vous aurez des problèmes, c’est certain. Pour ma part j’ai eu l’opportunité de croiser une SegFault. Un autre problème est la gestion du mot clé private qui n’a pas la même signification qu’en PHP.

Un peu de « syntax sugar »

La compilation permet de vous prévenir plus tôt de certaines erreurs (le typage et les valeurs de retour sont vérifiés et le compilateur retourne une Exception en cas de problème) mais aussi d’apporter de nouvelles fonctionnalités au language.

Par exemple il est possible de simplifier cette suite de isset et assignation :

if (isset($myArray[$key])) {
    $value = $myArray[$key];
    echo $value;
}

Par le mot clé fetch :

if fetch value, myArray[key] {
    echo value;
}

L’écriture des getters et des setters est elle aussi facilitée par de nouveaux mots clés qui les rendent totalement facultatifs :

namespace App;

class MyClass
{
    protected myProperty {
        set, get, toString
    };

    protected someProperty = 10 {
        set, get
    }
}

Nous pouvons aussi citer unlikely qui permet de faire de la prédiction de branche.

Un composant Symfony2 en extension PHP ?

J’ai souhaité traduire en extension un composant beaucoup utilisé dans les projets Symfony2 et affiliés, mon choix s’est porté sur l’EventDispatcher3. Il s’agit d’un composant stable, bien testé et qui fonctionne sous HHVM ce qui m’aurait permis de comparer les performances entre les deux solutions. Le projet est sur Github et je ne vous le cache pas : cela ne fonctionne pas malgré de nombreuses heures de bataille entre la logique voulue et celle générée par Zephir.

Le portage des classes consiste en théorie à déclarer quelques variables et à contourner les nombreuses limitations du langage. En pratique il m’aura fallu quelques heures de travail pour faire fonctionner la compilation, et les tests eux sont toujours rouges (seul un test est rouge sur toute la suite, mais cela suffit à considérer le composant inutilisable) – la promesse n’est donc pas encore tenue.

Une conclusion mitigée

Bien que le parseur soit écrit en PHP, les erreurs retournées sont parfois très très très obscures. J’ai passé plusieurs heures sur des problèmatiques purement liées au langage : il s’agît d’une vraie version Alpha avec de vrais bugs et approximations.

L’absence de stacktrace rend la résolution de problème bien plus complexe qu’en PHP et – dans l’état actuel le temps gagné en exécution ne vaut pas les cheveux que vous perdrez – notamment sur la manipulation des tableaux qui est très loin d’être fiabilisé selon moi.

La promesse de Zephir est belle ; et certaines features comme les Optimizer sont géniaux si votre besoin est de simplement appeler une fonction écrite en C depuis PHP. En effet avec une classe PHP et un fichier zep, vous pouvez compiler votre extension en moins de 5 minutes.

Dans l’écriture d’un projet complexe, il s’avère que je n’ai pas été capable de parvenir à mes fins avec la version 0.3.10a : probablement trop tôt, mais fort dommageable. PHP est un langage optimisé pour la vitesse de développement et non d’execution, Zephir aurait pu être l’entre deux parfait. A suivre, tout n’est pas noir et Phalcon 2 semble prometteur ; les performances de Zephir face a HHVM et même C sont très respectables et il faudra définitivement jeter un oeil nouveau dessus dans quelques mois.


  1. Phalcon2 est d’ailleurs écrit intégralement avec Zephir, contrairement à la version actuelle écrite en C. 

  2. Même si vous savez, l’open-source c’est mieux… 

  3. Le composant StopWatch a aussi été porté mais ne compilait pas lors du test. 

blog comments powered by Disqus