L'extension PHAR (en cours)

L'extension PHAR permet de mettre plusieurs fichiers php d'un seul fichier et de ne fournir ainsi qu'un seul fichier.
Ayant trouvé la chose intéressant je me suis penché dessus ....

Le

L'extension PHAR (pour « PHP Archive ») est native à partir de la version 5.3 de php (en PECL avant).

Utilité d'une telle chose ?

imaginons que nous ayons une "application" que l'on souhaite distribuer facilement et que son exploitation soit simple. Il est plus facile de fournir un fichier qu'une dizaine. Lors d'une mise à jour, plutôt que la faire au coup par coup on à un seul fichier à modifier.
Pour résumer :

  • Il permet d'encapsuler dans un unique fichier plusieurs fichiers.
  • Il est intégrable directement dans du code PHP.
  • Il est exécutable par PHP directement.
  • Il est possible de définir des métas-données.
  • Il supporte différents formats de compression.
  • Il est possible de le signer.

Concrètement comment ça se passe ?

Tous se passe autour de la classe Phar, celle ci permet la création et la modification des archive Phar a partir d'un fichier php.

Exemple de base :

<?php
// création de l’objet phar et indication du nom de fichier final
$phar = new Phar('fichierFinal.phar',0,'fichierFinal.phar');
// ajout d'un fichier dans l'archive phar
$phar->addFile('fichier.php');
$phar->stopBuffering();
?>

/!\ La classe Phar utilise, comme la plus part (toutes ?) des classes PHP le système d'exception pour la gestion d'erreur. Afin de traiter correctement les éventuelle erreur le code devrait donc être :

<?php
try {
	// création de l’objet phar et indication du nom de fichier final
	$phar = new Phar('fichierFinal.phar',0,'fichierFinal.phar');
	// ajout d'un fichier dans l'archive phar
	$phar->addFile('fichier.php');
	$phar->stopBuffering();
}
catch (Exception $e){
	echo $e->getmessage(); // A vous de voir comment traiter l'erreur ;)
}
?>

Je suis obligé de mettre une ligne par fichier que je veut dans le fichier ?
Non non, il existe une méthode permettant d'inclure directement un répertoire buildFromDirectory.
Fiou je suis rassuré la ;)

Comment utiliser le script de création ?

Vous pouvez pour cela :

  • Le faire en ligne de commande de la façon suivante : php -d phar.readonly=0 fichierdecreation.php
  • Directement avec le navigateur si et seulement si la directive phar.readonly est définie à off dans le php.ini (ou que vous le fassiez dans le script).

phar.readonly

Cette directive, activée par défaut, empêche toute modification d'une archive phar. Ceci est prévu pour un serveur de production où ce type de comportement ne semble pas utile.

A savoir

Les archives Phar possède un mécanisme permettant l'appel de code à l'inclusion de celle-ci. Par défaut il s'agit de l'appel au fichier 'index.php' (que celui-ci existe pas dans l'archive !!!).
ce phénomène n'est pas problématique si vous fournissez une appli complète, mais si c'est seulement une "librairie" cela le devient et vous aurez droit au message d'erreur suivant :

failed to open stream: phar error: "index.php" is not a file in phar ..

Manipulation de fichier dans une archive :

il est possible d'accèder à un fichier se trouvant dans une archive avec les fonctions de manipulation de fichier habituelle avec le flux phar://.

Par exemple

<?php
$fichier = file_get_content('phar://fichier.phar/fichiercontenu.php');
/* et même inclure juste un fichier */
include 'phar://fichier.phar/fichierContenuInclus.php';
?>

Exemple concret

Dans cet exemple nous allons une application qui gère et affiche les news d'un site. Elle est composée de :

  • index.php qui affiche les news
  • news.class.php // gestion des news
  • commentaireNews.class.php // gestion des commentaire des news

le tous est dans le répertoire news donc :

  • /creerphar.php
  • /news/index.php
  • /news/news.class.php
  • /news/commentaireNews.class.php


Création de l'archive

<?php
try {
    $phar = new Phar(__DIR__ . '/news.phar', Phar::CURRENT_AS_FILEINFO | Phar::KEY_AS_FILENAME,  'news.phar');
    $phar->buildFromDirectory(__DIR__ . '/news', '/.php$/');
    $phar->stopBuffering();
}
 catch (Exception $e){
     echo 'erreur PHAR : '.$e->getMessage()."rn".
             $e->getFile().
             "rn". $e->getFile() . 
             ."rn". $e->getTraceAsString();
 }
?>

Au passage vous voyer l'arrivé de constante dans le constructeur, pour plus d'info la doc. N'ayez pas peur j'ai pas trompé la classe phar est enfant de filesystemeiterator (enfin de RecursiveDirectoryIterator mais qui hérite de filesystemiterator).

Utilisation de l'archive

<?php
include 'news.phar';
?>

He oui c'est si simple avec un simple include on peut utiliser un système de news complet ;)

Mettre une application complète dans un fichier phar

Le mécanisme de chargeur de phar

Comme je l'indiqué plus haut les archives phar dispose d'un mécanisme de chargement automatique à l'appel du fichier avec

<?php 
include 'monphar.phar';
?>

A quoi ça peut bien servir ?

Par exemple à appeler directement notre fichier index.php sans que l'on fasse quoi que ce soit ;)
Il est aussi possible de l'utiliser pour appeler un fichier précis autre que l'index afin de réaliser une tache particulière (comme par exemple un autoload etc).

Peut on modifier ce chargeur ?

Bien entendu, tous est prévu pour cela avec la méthode setStub qui permet de définir (à partir de code php valide) un chargeur personnalisé.
Il est a noter que le chargeur doit toujours finir par __HALT_COMPILER(); et que le ?> final n'est pas obligatoire (PSR-2 recommande même de ne pas le mettre).

par exemple ré-écrivons le chargeur d'origine a titre d'exemple (à partir de l'exemple précédent)

<?php
try {
    $phar = new Phar(__DIR__ . '/news.phar', Phar::CURRENT_AS_FILEINFO | Phar::KEY_AS_FILENAME,  'news.phar');
    $phar->buildFromDirectory(__DIR__ . '/news', '/.php$/');
	$stub = <<<STUB
    <?php
    Phar::mapPhar('news.phar');
    require_once('phar://news.phar/autoload.php');
    spl_autoload_register('autoload');
    __HALT_COMPILER();
STUB;
    $phar->setStub($stub); //ajoute le chargeur
    $phar->stopBuffering();
}
 catch (Exception $e){
     echo 'erreur PHAR : '.$e->getMessage()."rn".
             $e->getFile().
             "rn". $e->getFile() . 
             ."rn". $e->getTraceAsString();
 }
?>

Le fichier autoload.php

<?php
function autoload($classe) {
	if (file_exists($classe.'class.php'))
		include $classe.'class.php';
}
?>

Comment utiliser tous ça ?

<?php
include 'news.php';
$news = new news();
$commentaire = new commentaireNews();
// etc
?>

A savoir

Lorsque l'on créer ainsi l'archive phar on prend le contenu du répertoire (à la racine du répertoire) mais l'on ne garde pas l'arborescence donc autoload.php qui, à la base, se trouve dans /news/ va être à la racine dans l'archive d'ou l'appel phar://news.phar/autoload.php. On vois que dans le chargeur on utilise la syntaxe d'ouverture de flux comme si l'on était à l'extérieur de l'archive.

Les méta data

Il est possible d'ajouter des méta data, et ainsi indiquer des informations intéressante comme la version l'auteur, une description etc.
Pour cela nous pouvons utiliser la méthode setMetadata.
Exemple :

<?php
try {
    $phar = new Phar(__DIR__ . '/news.phar', Phar::CURRENT_AS_FILEINFO | Phar::KEY_AS_FILENAME,  'news.phar');
    $phar->buildFromDirectory(__DIR__ . '/news', '/.php$/');
	$phar->setMetadata(array(
        'version' => '412',
        'author' => 'Moogli',
        'description' => 'Ben ça sert à aafficher des news hein ;)'
        )
);
    $phar->stopBuffering();
}
 catch (Exception $e){
     echo 'erreur PHAR : '.$e->getMessage()."rn".
             $e->getFile().
             "rn". $e->getFile() . 
             ."rn". $e->getTraceAsString();
 }
?>

ainsi en ligne de commande vous pouvez utiliser php news.phar -i pour retrouver ces données ou avec la méthode http://fr2.php.net/manual/en/phar.getmetadata.php.

Peut ont créer une archive contenant autre que des fichiers php

buildFromIterator

l'exemple de la doc

<?php
// crée avec l'alias "projet.phar"
	$phar = new Phar('projet.phar', 0, 'projet.phar');
	$phar->buildFromIterator(
    new RecursiveIteratorIterator(
     new RecursiveDirectoryIterator('/chemin/vers/projet')),
    '/chemin/vers/projet');
$phar->setStub($phar->createDefaultWebStub('cli/index.php', 'www/index.php'));
?>

Avez ça vous pouvez inclures tous ce qu'il y a dans les sous répertoires du chemin par défaut que cela soit du php ou autre.

Liens

Les liens qui m'ont permis de découvrir la chose :
Phar : PHP Archive
Phar sur le Blob mageekbox

Surement a compléter !