PHP 5.3 : phar : PHP Archive

le - Lien permanent 11 commentaires

Les exemples correspondant à ce point se trouvent dans le répertoire “phar”.

Java a ses .jar, UNIX[1] ses .tar, … Et PHP, jusqu’à aujourd’hui, n’avait que des fichiers, sans la possibilité de les regrouper en une seule archive.

PHP 5.3 corrige ce manque, en intégrant en natif l’extension phar — pour « PHP Archive » — qui permet de regrouper plusieurs fichiers en un seul paquet, reconnu et interprété par le moteur de PHP.

Sommaire :


Vais-je réussir à résister pendant tout un article à la tentation de faire un — ou plus — jeux de mots sur « Phar »[2] ?


Introduction

PHP manque, depuis longtemps, d’une solution permettant de regrouper plusieurs fichiers d’une bibliothèque, ou d’une application, en une seule archive, à la façon des .jar du monde JAVA, dont le côté pratique est des plus appréciables lorsqu’il s’agit d’ajouter un nouveau composant à une application existante, ou de mettre à jour une librairie.

Des solutions existent depuis de nombreuses années — je pense, par exemple, au paquet PEAR PHP_Archive — mais sans intégration native à PHP, elles n’ont pas rencontré un succès phénoménal…

En réponse, PHP 5.3 intègre désormais nativement l’extension phar ; en espérant que cela mène à sa popularisation, et, pourquoi pas, que les fonctionnalités qu’elle offre se trouvent utilisées à plus large échelle !


Pour information, si phar est activée sur votre installation, vous devriez voir apparaitre en sortie de phpinfo un bloc ressemblant à celui que je reproduis ci-dessous :

phar-phpinfo.png

Dans le cas contraire, revoyez votre installation, si vous en avez la possibilité… Ou maudissez votre hébergeur ^^


Une première Phar

Pour commencer, nous ferons simple : une archive Phar ne contenant que deux fichiers, l’un incluant l’autre.

Fichiers de notre archive

Le principe est le suivant : nous créons deux fichiers :

index.php :

<?php

require_once('included.php');

echo "Hello, $who!\n";

et included.php :

<?php

$who = 'World';

Mais nous ne voulons pas être forcés de distribuer deux fichiers : pour nos utilisateurs, ne déployer un seul fichier sur leur serveur serait tellement plus agréable !


Création d’une première archive

Nous allons donc créer un troisième fichier, que je nommerai make-archive.php.

Ce script sera responsable de la création d’une archive, nommée ici « application.phar.php », qui contiendra les deux précédemment cités.

Ce regroupement de plusieurs fichiers au sein d’une seule archive se fait en utilisant la nouvelle classe Phar, et sa méthode addFile, et nous écrivons le script de création d’archive qui suit :

<?php

$phar = new Phar(__DIR__ . '/application.phar.php');
$phar->addFile('index.php');
$phar->addFile('included.php');

Il est à noter, avant d’aller plus loin, que la création de l’archive n’est possible que si phar n’est pas configuré en readonly.
Vous devrez donc probablement spécifier que la directive de configuration phar.readonly=0 doit être à 0 lorsque vous appelerez ce script :

php -d phar.readonly=0 make-archive.php

L’autre solution étant, bien entendu, de régler cette directive directement au sein du fichier de configuration php.ini.


Utilisation de cette archive

Pour utiliser cette archive, deux possibilités :

En ligne de commande, vous exécutez le script application.phar.php — notre archive — comme n’importe quel autre programme PHP :

php@php53:~/php53/phar/exemple-0
$ php ./application.phar.php

Et vous obtiendrez en sortie :

Hello, World!

Et cela fonctionne aussi si vous appelez votre archive via Apache, avec une URL de la forme suivante : http://172.16.133.128/phar/exemple-0/.
Vous obtiendrez alors, normalement, un affichage du même type :

Hello, World!

En somme, votre archive, tout en regroupant plusieurs fichiers en un seul (faites l’essai : supprimez les deux fichiers de départ !), se comporte exactement comme vos scripts de départ !

D’ailleurs, si vous jetez un œil à son contenu, vous verrez qu’il commence par du code PHP, permettant d’extraire les contenus de ce qui étaient deux fichiers, et qu’il se termine, à peu de chose près, par… le contenu de vos deux fichiers ;-)


Archive Phar et manipulations de fichiers

Avec l’ajout des fonctionnalités de support d’archives, apparait un nouveau stream pour les manipuler.

Vous l’aurez deviné, il s’agit du stream phar://, qui peut être utilisé avec les principales fonctions de manipulation de fichiers, dont fopen, mkdir, unlink, stat, ou même des itérateurs tels RecursiveDirectoryIterator.

Pour donner quelques exemples, supposons que nous avons à notre disposition une archive Phar, contenant les fichiers suivants :

.
|-- css
|   `-- style.css
|-- images
|   `-- hello-world.png
`-- index.php

Lorsque vous lirez la suite de cet article, vous reconnaitrez probablement cette archive : elle correspond à celle que nous génèrerons un peu plus bas, pour montrer qu’une Phar peut contenir autre chose que des fichiers PHP.


Manipulations de lecture

Il est possible de récupérer le contenu d’un des fichiers de l’archive, par exemple en utilisant file_get_contents :

$str = file_get_contents('phar://' . __DIR__ . '/application.phar.php/css/style.css');

echo '<pre>';
echo $str;
echo '</pre>';

Et nous obtiendrons en sortie quelque chose ressemblant à ceci :

p {
    color: red;
    font-weight: bold;
}


Si nous essayons d’accéder à un fichier inexistant au sein de la Phar :

$str = file_get_contents('phar://' . __DIR__ . '/application.phar.php/not-found.txt');

Alors, nous obtenons un avertissement :

phar-read-file-not-found-warning.png

Exactement comme si nous travaillions avec des fichiers « normaux », non regroupés au sein d’une archive.


Autre exemple, en utilisant des itérateurs pour parcourir le contenu de notre archive :

$filePath = 'phar://' . __DIR__ . '/application.phar.php';

$files = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($filePath),
    RecursiveIteratorIterator::SELF_FIRST);
foreach($files as $name => $file){
    var_dump($name);
}

Et, fort logiquement, nous obtenons en sortie :

string 'phar:///home/php/php53/phar/stream-phar-1/application.phar.php/css' (length=66)
string 'phar:///home/php/php53/phar/stream-phar-1/application.phar.php/css/style.css' (length=76)
string 'phar:///home/php/php53/phar/stream-phar-1/application.phar.php/images' (length=69)
string 'phar:///home/php/php53/phar/stream-phar-1/application.phar.php/images/hello-world.png' (length=85)
string 'phar:///home/php/php53/phar/stream-phar-1/application.phar.php/index.php' (length=72)

En somme, le travail au sein d’une Phar se fait de la même manière qu’avec des fichiers « habituels » — et c’est tant mieux !


Manipulations d’écriture

Essayons maintenant de passer à des opérations d’écriture : commençons par créer un nouveau fichier au sein de notre archive, puis lisons son contenu, pour nous assurer de sa bonne création, et, enfin, supprimons le, afin de restaurer notre archive dans son état initial :

$str = 'Hello, World!';

// Création d'un "sous-fichier" dans l'archive Phar
$a = file_put_contents('phar://' . __DIR__ . '/application.phar.php/test.txt', $str);
var_dump($a);

// Lecture, pour vérifier :
$read = file_get_contents('phar://' . __DIR__ . '/application.phar.php/test.txt');
var_dump($read);

// Et suppression :
$a = unlink('phar://' . __DIR__ . '/application.phar.php/test.txt');
var_dump($a);

Quelques points sont à noter, ici — vous vous en rendrez peut-être compte si vous essayez d’exécuter cette portion de code…

Avant tout, pour pouvoir effectuer des opérations d’écriture au sein d’une Phar, il est indispensable que la directive phar.readonly ne soit pas activée, typiquement dans votre fichier php.ini… Et, par défaut, probablement pour des raisons de sécurité, elle a tendance à l’être !

Si phar.readonly est activée, toute tentative d’écriture au sein de la Phar se soldera par un échec, de la forme suivante :

phar-write-operations-disabled.png

Ensuite, une fois ce point réglé, n’oublions par qu’une PHP Archive est avant tout un fichier ; vous — ou votre serveur Web — devez donc disposer des droits d’écriture dessus ; dans le cas contraire… Paf :

phar-write-permission-denied.png

Une fois ce second point réglé… Vous pouvez manipuler les fichiers présents au sein de votre archive, en toute liberté ;-)


Archive Phar contenant une bibliothèque

La fonctionnalité de « PHP Archive » permet avant tout de regrouper plusieurs fichiers en un seul. Il est donc envisageable qu’elle soit utilisée comme moyen de packaging de fichiers d’une bibliothèque[3].

Mini-application utilisant une bibliothèque externe

Nous allons donc, à présent, mettre en place une mini-application, basée sur trois fichiers :

index.php :

require_once(__DIR__ . '/libs/A.php');
require_once(__DIR__ . '/libs/B.php');

echo '<pre>';
echo A::add(10, 20) . "\n";
$b = new B('World');
$b->sayHello();
echo '</pre>';

Qui fait appel aux deux fichiers suivants, se trouvant tous deux dans un répertoire nommé libs :

A.php :

<?php

class A {
    public static function add($a, $b) {
        return $a + $b;
    }
}

Et B.php :

<?php

class B {
    protected $str;
    public function __construct($str) {
        $this->str = $str;
    }
    public function sayHello() {
        echo "Hello, {$this->str}!\n";
    }
}

L’usage traditionnel serait de distribuer cette bibliothèque sous forme d’un fichier tar.gz ou assimilé, et que sa décompression par un utilitaire non-php amène à la création d’un répertoire libs et de ses deux fichiers…


Créer l’archive contenant la bibliothèque

Mais, puisque nous disposons des fonctionnalités de l’extension phar, pourquoi ne pas les utiliser pour regrouper ces deux fichiers en un seul ?

Cela peut se faire à l’aide du script suivant — que j’ai encore une fois nommé make-archive.php :

$phar = new Phar(__DIR__ . '/libs.phar', 0, 'libs.phar');
$phar->buildFromDirectory(__DIR__ . '/libs', '/\.php$/');
$phar->stopBuffering();

Notez au passage que, cette fois-ci, je n’ai pas pris la peine d’ajouter chaque fichier manuellement à l’aide de la méthode addFile, et que j’ai préféré ajouter d’un coup tous les fichiers .php se trouvant dans la répertoire libs, en faisant appel à la méthode buildFromDirectory de la classe Phar.
Principal intérêt : si je rajoute une classe dans ma bibliothèque, je n’aurai pas à modifier le script de création de mon archive.

Exécuter ce script à l’aide de la commande suivante :

php -d phar.readonly=0 make-archive.php

Entrainera la création du fichier libs.phar, contenant nos deux classes.


Utiliser une bibliothèque depuis une Phar

Pour, maintenant, utiliser l’archive libs.phar, et plus les deux fichiers distincts que nous utilisions auparavant, il nous faut modifier notre fichier index.php : nous ne devons plus inclure nos deux anciens fichiers, mais seulement notre archive nouvellement créée.

Le fichier « maître » de notre application devient donc :

require_once(__DIR__ . '/libs.phar');

echo '<pre>';
echo A::add(10, 20) . "\n";
$b = new B('World');
$b->sayHello();
echo '</pre>';

Et la sortie de notre application ne change pas ; elle ressemble toujours à ceci :

30
Hello, World!

Finalement, pour l’utilisateur de notre librairies, deux différences :

  • Il n’a plus qu’un seul fichier à déposer sur son serveur
    • Ce qui peut signifier qu’il n’a plus qu’un seul fichier à télécharger,
    • ou qu’il n’a plus à passer par l’étape de décompression.
  • Et il n’a plus qu’un seul fichier à inclure !

En termes de confort, il est donc gagnant ;-)


Une application entière dans une Phar

Nous venons de voir qu’il était possible de découper notre application en deux :

  • Les sources de notre application, sous forme d’un ou plusieurs fichiers .php,
  • Et la ou les bibliothèques utilisées, sous forme d’une ou plusieurs Phar.

Pourquoi ne pas aller plus loin, et ne pas distribuer notre application toute entière sous forme d’un seul fichier ? Est-ce qu’il ne serait pas intéressant pour les utilisateurs de notre application de n’avoir qu’un et un seul fichier à déposer sur leur serveur ?


Notre application, un brin revisitée

Réécrivons quelques portions de notre application, qui se compose à présent des trois fichiers suivants :

application/index.php :

require_once(__DIR__ . '/A.php');
require_once(__DIR__ . '/B.php');

echo '<pre>';
echo A::add(10, 20) . "\n";
$b = new B('World');
$b->sayHello();
echo '</pre>';

application/A.php :

class A {
    public static function add($a, $b) {
        return $a + $b;
    }
}

Et application/B.php :

class B {
    protected $str;
    public function __construct($str) {
        $this->str = $str;
    }
    public function sayHello() {
        echo "Hello, {$this->str}!\n";
    }
}

Si nous pouvons regrouper en une seule archive ces trois fichiers, et si index.php est automatiquement appelé lorsque nous « exécutons » notre archive, nous aurons réussi à disposer d’une application constituée, pour l’utilisateur, d’uniquement un seul fichier !


Construction de l’archive

La construction de l’archive se fera de la même manière que précédemment, en ajoutant à une Phar l’ensemble des fichiers présents dans le répertoire application :

$phar = new Phar(__DIR__ . '/application.phar.php', 0, 'application.phar.php');
$phar->buildFromDirectory(__DIR__ . '/application', '/\.php$/');
$phar->stopBuffering();

Et une fois ce script exécuté, avec une commande telle celle-ci :

php -d phar.readonly=0 make-archive.php

Nous disposerons d’un seul et unique fichier, application.phar.php, contenant tous les fichiers PHP de notre application !

Il ne vous reste plus qu’à appeler directement une URL de la forme suivante dans votre navigateur pour le constater :

http://172.16.133.128/phar/exemple-2/application.phar.php/index.php

Vous obtenez en sortie, encore une fois, quelque chose de ce type :

30
Hello, World!

N’est-ce pas tentant ? Et pour vos utilisateurs ?


Phar et fichiers non PHP

Tant que nous y sommes, pourquoi ne pas aller encore plus loin ?

Nous venons de voir qu’il était possible de distribuer tous les fichiers .php d’une application sous la forme d’un seul fichier, une Phar.
Mais une application Web constituée uniquement de fichiers .php, cela n’existe pour ainsi dire pas : nous travaillons toujours avec des images, des fichiers Javascript, des CCS, …

Et bien, et c’est tant mieux, Phar permet aussi d’incorporer ces fichiers là dans notre archive !

Application d’exemple

Par exemple, considérant une application, simplissime, constituée des fichiers suivants :

.
|-- css
|   `-- style.css
|-- images
|   `-- hello-world.png
`-- index.php

Le fichier index.php ressemblera à quelque chose de ce style :

<?php echo '<?xml version="1.0" encoding="UTF-8" ?>'; ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
<head>
    <title>Application Phar avec fichiers externes</title>
    <link href="css/style.css" media="screen" rel="stylesheet" type="text/css" />
    <meta name="robots" content="noindex,nofollow" />
    <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
</head>
<body>
    <p>
        Hello, world!
    </p>
    <p>
        <img src="images/hello-world.png" alt="Hello, world!" />
    </p>
</body>
</html>

Et pour ce qui est de la feuille de style, nous ferons au plus simple :

p {
    color: red;
    font-weight: bold;
}

L’image, quand à elle… heu, on verra mes talents de dessinateur plus tard :-D

Regrouper nos fichiers en une Phar

Notre script de création d’archive va devoir être légérement modifié, puisque nous ne souhaitons plus intégrer à notre archive uniquement des fichiers .php :
(Vous noterez que je travaille sous SVN, et je ne veux pas inclure les répertoires .svn à mon archive)

$phar = new Phar(__DIR__ . '/application.phar.php', 0, 'application.phar.php');
$phar->buildFromDirectory(__DIR__ . '/application', '/^.+\..+$/');
$phar->stopBuffering();

Encore une fois, exécuter ce script mènera à la création de notre Phar : application.phar.php.

Utilisation de notre application

Maintenant que notre application a été créée, il ne nous reste plus qu’à l’utiliser, en y accédant via une URL de la forme suivante :

http://172.16.133.128/phar/fichiers-non-php/application.phar.php

Qui redirige automatiquement vers

http://172.16.133.128/phar/fichiers-non-php/application.phar.php/index.php

Du fait que Phar a conservé la convention usuellement mise en place sous Apache + PHP : « les fichiers par défaut s’appellent index.php ».

Et nous obtenons à l’écran, pour notre plus grand plaisir :

phar-all-in-one.png

Au niveau des logs Apache, si vous êtes curieux, voici ce que nous obtenons :

172.16.133.1 - - [19/Nov/2008:01:43:18 +0100] "GET /phar/fichiers-non-php/application.phar.php/index.php HTTP/1.1" 200 648 "-" "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.4) Gecko/2008110400 Ubuntu/8.04 (hardy) Firefox/3.0.4 FirePHP/0.2.1"
172.16.133.1 - - [19/Nov/2008:01:43:18 +0100] "GET /phar/fichiers-non-php/application.phar.php/css/style.css HTTP/1.1" 200 44 "http://172.16.133.128/phar/fichiers-non-php/application.phar.php/index.php" "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.4) Gecko/2008110400 Ubuntu/8.04 (hardy) Firefox/3.0.4 FirePHP/0.2.1"
172.16.133.1 - - [19/Nov/2008:01:43:18 +0100] "GET /phar/fichiers-non-php/application.phar.php/images/hello-world.png HTTP/1.1" 200 3300 "http://172.16.133.128/phar/fichiers-non-php/application.phar.php/index.php" "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.4) Gecko/2008110400 Ubuntu/8.04 (hardy) Firefox/3.0.4 FirePHP/0.2.1"

Au rang des avantages, donc : un seul fichier à distribuer et à installer…

Mais un passage par PHP pour servir chacun des fichiers de la Phar, y compris ceux que nous aurions auparavant qualifié de « statiques »…
A éviter pour une application soumise à des contraintes de forte charge, donc, peut-être ? Mais par contre, pour une petite application peu utilisée, le gain de temps en installation peut être intéressant…


Archive compressée

Jusqu’à présent, nous avons travaillé avec des archives les plus simples qui soient, qui n’étaient finalement qu’un regroupement de plusieurs fichiers en un seul.

Phar permet d’aller un peu plus loin, en nous offrant la possibilité de compresser les fichiers stockés au sein de nos archives, en utilisant soit la compression gzip, soit la compression bzip2.

Dans un cas comme dans l’autre, c’est au moment de la création de l’archive que nous spécifions qu’il convient de compresser le contenu de celle-ci, à l’aide de la méthode compressFiles de la classe Phar.

Par exemple, le script de création d’archive utilisé pour notre application intégrant une image et une CSS pourrait être revu, pour devenir le suivant :

$phar = new Phar(__DIR__ . '/application.phar.php', 0, 'application.phar.php');
$phar->buildFromDirectory(__DIR__ . '/application', '/^.+\..+$/');
$phar->compressFiles(Phar::GZ);
$phar->stopBuffering();

Ici, nous utiliserions gzip comme méthode de compression.

Pour travailler avec bzip2, il aurait fallu utiliser :

$phar = new Phar(__DIR__ . '/application.phar.php', 0, 'application.phar.php');
$phar->buildFromDirectory(__DIR__ . '/application', '/^.+\..+$/');
$phar->compressFiles(Phar::BZ2);
$phar->stopBuffering();

A noter, avant que vous ne souhaitiez tester : la compression via gzip n’est possible que si l’extension gzip est compilée/installée ; même chose pour la compression bzip2, qui dépend de l’extension bz2.

Typiquement, pour pouvoir tester ces deux portions de code, il vous faudrait avoir compilé PHP en incluant les deux directives suivantes :

--with-zlib --with-bz2

Pour plus de détails, Cf l’article Compilation et installation de PHP 5.3 depuis les sources.


Phar et stub

Jusqu’à présent, nous avons toujours travaillé avec des cas « simples », où le comportement d’exécuter par défaut le contenu du fichier index.php contenu à la racine de l’archive était suffisant.

Cela dit, il n’en n’est pas toujours ainsi, et il arrive que lancer notre application soit plus difficile que cela. Typiquement, il se peut que, pour fonctionner, votre application ne passe pas par du code impératif, et nécessite qu’une méthode précise d’une classe précise soit appelée — cet appel peut même être précédé de portions de code « utilitaires », telles, par exemple, l’enregistrement d’une fonction d’autoloading…

Pour répondre à ce genre de besoins, la classe Phar exporte une méthode, nommée setStub, qui permet de définir la portion de code qui sera exécutée pour « lancer » l’application.

Cette méthode doit être appelée à la création de l’archive, et reçoit en paramètre le code qui doit être appelé lorsque l’utilisateur fera appel à l’archive, sans préciser en paramètre le nom d’un des fichiers la composant.


Application d’exemple

Comme exemple, imaginons l’application constituée des quatre fichiers suivants :

application.php :

Ce fichier est le coeur de l’application, et en contient la classe principale :

class Application {
    public static function main() {
        echo A::add(10, 20) . "\n";
        $b = new B('World');
        $b->sayHello();
    }
}

libs/autoload.php :

Ce fichier contient le gestionnaire d’autoload de notre application :

class Autoload {
    public static function doAutoload($className) {
        require_once(__DIR__ . DIRECTORY_SEPARATOR . $className . '.php');
    }
}

Puis viennent les deux bibliothèques auxquelles nous sommes déjà habitués :

libs/A.php :

class A {
    public static function add($a, $b) {
        return $a + $b;
    }
}

Et, pour finir, libs/B.php :

class B {
    protected $str;
    public function __construct($str) {
        $this->str = $str;
    }
    public function sayHello() {
        echo "Hello, {$this->str}!\n";
    }
}

Le lancement de l’application passe par deux étapes :

  • Chargement du fichier d’autoload,
  • Appel à la méthode Application::main.


Création de l’archive

Une solution, basée sur les exemples que nous avons vu plus haut, serait de créer un fichier nommé index.php, et de placer les deux opérations de chargement / lancement de notre application dans celui-ci.

Ensuite, lorsque l’utilisateur essaye d’exécuter notre Phar, c’est ce fichier index.php qui sera exécuté, et notre application sera lancée.

Mais il est aussi possible de définir le code de lancement de notre application en faisant appel à la méthode setStub lors de la création de l’archive, qui peut alors se faire à l’aide d’un script make-archive.php de ce type :

$phar = new Phar(__DIR__ . '/application.phar.php', 0, 'application.phar.php');

$phar->buildFromDirectory(__DIR__ . '/application', '/\.php$/');

$stub = <<<ENDSTUB
<?php
Phar::mapPhar('application.phar.php');
require_once('phar://application.phar.php/libs/autoload.php');
spl_autoload_register(array('Autoload', 'doAutoload'));

require_once('phar://application.phar.php/application.php');
Application::main();

__HALT_COMPILER();
ENDSTUB;
$phar->setStub($stub);

$phar->stopBuffering();

En exécutant ce script, nous créerons une archive qui, plutôt que de commencer par le code de chargement par défaut d’une Phar, commencera par notre propre code de chargement — je vous encourage vivement à ouvrir le fichier application.phar.php à l’aide d’un éditeur de texte : il est en grande partie lisible, et vous verrez ainsi plus précisément ce que je veux dire : il commence par le code suivant :

<?php
Phar::mapPhar('application.phar.php');

require_once('phar://application.phar.php/libs/autoload.php');
spl_autoload_register(array('Autoload', 'doAutoload'));

require_once('phar://application.phar.php/application.php');
Application::main();

__HALT_COMPILER(); ?>

Et, ensuite seulement, contient le code qui se trouvait réparti dans nos différents fichiers sources.


Pouvoir visionner ainsi le code source de vos archives Phar est une raison pour laquelle je préfére travailler avec des archives Phar non compressées, en particulier pendant que mes applications sont en phase de développement.

Nous avons vu plus haut que nous pouvons ensuite décider de compresser le contenu de nos archives, afin de rendre les fichiers d’archives plus petits — et plus facilement distribuables ?


Lancement de l’application

A l’appel de l’archive application.phar.php, la portion de code que nous avons spécifié à l’aide de setStub sera appelée.

Ici, donc, nous brancherons la fonction d’autoloading nécessité par notre application, et appellerons sa méthode principale : notre application, même si elle ne dépend pas d’un fichier nommé index.php, sera alors lancée !


Aller plus loin ?

Pour terminer, un peu en vrac, voici quelques idées, pour aller plus loin que ce que j’ai présenté au cours de cet article…


Quelques cas d’utilisation

Bien entendu, le support natif des Phar par PHP est encore tout récent, puisqu’aucune version stable de PHP n’est sortie en l’incluant par défaut… Mais si vous êtes curieux, vous pouvez jeter un coup d’oeil aux deux expérimentations qui suivent, qui montrent déjà ce qu’il est possible de faire en exploitant le support des Phar :

Le second point, même s’il ne s’agissait que d’une expérimentation qui date déjà quelque peu, me semble, dans l’idée, particulièrement intéressant : plutôt que de passer par PEAR pour installer PHPUnit et ses dépendances, pourquoi ne pas uniquement télécharger un seul fichier, permettant directement de lancer des tests automatisés ?

Dans ce cas, on est sur une application en ligne de commande, qui n’a pas de besoin extrême en termes de performances… Est-ce que ce n’est pas un exemple parfait où la distribution sous forme d’une Phar pourrait simplifier la vie des développeurs et utilisateur ?

A tout hasard, quelle application grand-public sera la première à proposer aux développeurs de distribuer leurs plugins sous forme d’archives Phar, et aux utilisateurs ne ne plus avoir à déployer qu’un seul fichier ?

Encore une fois[4], j’ai hâte de voir comment le support natif des Phar sera utilisé, et les gains que nous en tirerons, à la fois en tant que développeurs et en tant qu’utilisateurs !


Phar : sécurité et signature

Je n’en n’ai pas parlé dans cet article, mais il est à noter que Phar dispose d’un mécanisme de signature, permettant de valider le contenu d’une archive, et son éventuelle non-altération.

Pour plus d’information, je vous encourage à consulter les pages de manuel en rapport avec le sujet :

Si vous avez l’occasion de travailler avec des Phar signées, qu’en pensez-vous ? Avez-vous rencontré des difficultés ?
Si c’est le cas, laissez un commentaire ;-)


Phar : accès à des fichiers externes

Pour finir, notez qu’il est possible d’accéder, depuis l’application packagée sous forme d’une Phar, à des fichiers externes à cette archive.

Pour cela, il convient de faire appel à la méthode Phar::mount, pour spécifier qu’un fichier ne doit pas être lu depuis l’archive, mais depuis l’environnement au sein de laquelle celle-ci est déployée.

Un exemple d’utilisation serait le suivant :

  • Vous définissez au sein de votre archive un fichier de configuration par défaut
  • Au lancement de votre application, s’il n’existe pas de fichier de configuration « externe », vous le crééez, en copiant le contenu du fichier de configuration par défaut,
  • L’utilisateur de votre application peut alors modifier la configuration de votre application, sans avoir à aller modifier un fichier packagé à l’intérieur d’une Phar !
  • Et, par la suite, vous utilisez le fichier de configuration externe, modifié par l’utilisateur.

Si vous êtes curieux, vous pouvez jeter un coup d’oeil sur l’exemple external-files, dans l’archive jointe à cet article, qui met en place ce principe ;-)


Pour terminer, que dire ? Que PHP 5.3 propose encore une idée intéressante ? Oui !

Et vous, qu’en pensez-vous ? Utiliseriez-vous des PHP Archive ? Pourquoi ? Pourquoi pas ?


Notes

[1] En fait, pour les .tar, un peu tout le monde, maintenant ^^

[2] Probablement pas… entre les « A long time ago, in a galaxy phar, phar away… » et les « C’est une fonctionnalité Phar », je vais finir dingue ^^ — Bon, ok, trop tard pour les jeux de mots :-D

[3] Note : je n’ai pas dit qu’utiliser une Phar comme moyen de packaging d’une bibliothèque était forcément une bonne idée — je me pose notamment des questions en termes de performance — mais uniquement que c’était un usage possible !

[4] j’ai l’impression de dire ça pour la moitié des articles de cette série ^^

Vous avez apprécié cet article ? Faites le savoir !

Commentaires

1. Par Olivier le 2008-11-19 08:26
Olivier

À vu de nez, si pour créer un archive il est nécessaire de se cogner un script propre à l'extension, ça ne va pas aller bien loin. Java a ses .jar mais ce sont surtout des fichiers zip, idem pour les .xpi chez Mozilla et il me semble que le mécanisme des egg Python est aussi basé sur zip. Bref, il aurait été de bon ton de faire quelque chose dans ce goût là, le déployer d'office et fournir du même coup les lib pour zip/unzip.

Je vois beaucoup de choses intéressantes dans 5.3 mais j'ai très peur du numéro de version qui va inciter des hébergeurs à mettre en jour en pensant que c'est une révision mineure alors que le saut est énorme et va peut-être causer plus de problèmes qu'un passage de 4.1 à 5.(0,1,2)

En tout cas, merci de tenir ce blog, c'est fort utile :)

2. Par Laurentj le 2008-11-19 10:44
Laurentj

Je ne suis pas particulièrement convaincu par phar, pour plusieurs raisons :

1) c'est bien plus lent à executer (forcément...), surtout si l'archive est compressée
2) il n'y a aucun système d'installation/desinstallation/mise à jour : pour fonctionner, bien souvent on doit passer par une phase d'installation. Ça aurait été bien de proposer un système de hook pour installer/desinstaller/mettre à jour (mais mieux foutu que ce que propose pear)
3) quid de tout ce qui est mécanisme de cache http, des intéractions avec apache au niveau des entetes, htaccess et cie ?

En fait, je ne vois pas d'intérêt à utiliser phar. Quand on livre un zip/gz d'une appli, décompresser ces archives n'est pas un réèl souci.

3. Par Renaud le 2008-11-19 11:14
Renaud

Quelques petites corrections, me semble-t-il:

"Pour information, si phar est activée sur votre installation, vous devriez voir apparaitre dans votre fichier php.ini un bloc ressemblant à celui que je reproduis ci-dessous :"

C'est pas plutot le phpinfo() ?

"En somme, votre archive, tout en regroupant plusieurs fichiers en un seul"

"Du fait que Phar a conservé la convention usuelle à PHP : « les fichiers par défaut s’appellent index.php »."

Heu... non, c'est Apache qui gère les fichiers à exécuter lorsqu'ils ne sont pas spécifiés dans l'url !

Sinon, moi je voyais plutot Phar comme une archive d'installation, c'est à dire comme une archive distribuable contenant le code de l'application ainsi que du code pour installer/mettre à jour/désinstaller l'application. Je ne doute pas que quelqu'un va pondre un système pareil... distribué sous forme de fichier Phar, inclus dans le Phar de votre application à distribuer par exemple :)

4. Par Pascal MARTIN le 2008-11-19 12:25
Pascal MARTIN

Hello !

Olivier > Un outil "générique" répondrait-il aux besoins de nos applications, avec toutes leurs spécificités ? Surtout considérant que je ne vois pas Phar utilisé pour "n'importe quel" projet, mais uniquement dans des cas spécifiques.

Par contre, je suis on ne peut plus d'accord avec toi sur le numéro de version de ce PHP 5."et demie"... et des implications que cela peut avoir derrière... Même si j'ai en même temps hâte d'en profiter !

Et merci :-)
(Ca m'est utile à moi aussi, en fait : rédiger ces articles me pousse à fouiller plus que je ne le ferais sinon -- même si je me dis parfois que je pourrais fouiller encore plus (typiquement, pour cet article sur Phar, j'aurais bien fait quelques benchs ; mais pas eu le temps... Si personne n'en fait dans les prochaines semaines, peut-être que ça donnera un autre article ^^ ) )



Laurentj >
Autant pour une appli Web "lourde", je suis d'accord avec toi... Autant pour une application plus légére, ou d'usage plus occasionnel (phpMyAdmin ? Un équivalent à Trac ? ), pourquoi pas ?
Bon, OK, il faut encore installer PHP, Apache, configurer le tout, ... Pas encore parfait, donc...

Typiquement, sur des applis de ce genre, on aura moins tendance à sortir un cache d'opcode, ou à se soucier des performances, que sur une application grand-public -- enfin, avis perso ^^

Décompresser un zip n'est pas un réel soucis, je l'admet -- mais ensuite, il faut toujours donner des droits d'écriture sur certains fichiers, certains répertoires, ... rien que ça, c'est lourd. Si Phar permet d'alléger la charge de travail à ce niveau, ça ne peut pas faire de mal !

D'ailleurs, il faudrait que je teste, un jour... Si on pouvait utiliser Phar pour packager des applications en php-gtk, ça ferait des applications distribuables en un seul fichier... Limite juste à cliquer sur une icone, et hop, l'application se lance, sans avoir à déployer ni rien...
... Et idéalement, avec sauvegarde d'une partie des préférences directement au sein de la Phar => copier l'archive copie aussi la conf et les préférences...
Rhhhaaaa ça me donne envie de tester ^^



Renaud >
Merci pour ces remarques ; peu importe combien de fois je relis / relirai, il y en aura toujours qui passeront à la trappe :(-
Pour le dernier point, nous sommes d'accord ; je me suis peut-être mal exprimé, par contre => j'ai modifié ma formulation, pour mentionner à la fois PHP et Apache.

Pour ce qui est de l'idée d'utiliser une Phar comme "installeur"... Intéressant ; à creuser, d'ailleurs %)
(Et un point de plus dans ma todo-list ^^ )

5. Par foxmask le 2008-11-19 21:58
foxmask

Bonsoir,
Que Phar permette de faire des WAR-like serait très très pratique.
on met son toto.phar dans le doc root apache ; on fait un http://localhost/toto et vlan ; l'appli est utilisable immediatement (en omettant la partie installation du SGBD puisqu'un WAR ne le fait pas non plus on va pas pousser, il faut donc à minima configurer sa base avant ;)

Il me semble qu'avec les War et Apache Tomcat par exemple, l'acces à http://localhost/toto déclenche "l'éclatement" de l'archive WAR qui est mis dans un rép du cache du tomcat.

Mais peut-etre que cela depasserait le "cadre" fixé par Phar et qu'il faudrait que le mod php permette se comportement "d'éclatement" + mise en cache.

Enfin Cela permettrait de partager plus que des "mini" appli et le pied de se dire qu'installer une appli php se résumerait à "un phar, une base installée et c'est pret"

-my 2 coins-

6. Par Jérémy le 2008-11-20 10:22
Jérémy

Article très intéressant. Moi aussi la première idée d'utilisation de phar serait les instaleurs.
Actuellement, je n'ai rien trouvé de mieux que d'embarquer une archive encodé en base64 dans un setup.php que je déploie et décompresse dans un dossier temp.

Un article sur les performances (CPU et Mémoire) serait très intéressant !

Est ce que l'intégralité de l'archive est chargé en mémoire a chaque appel (comme c'est le cas des includes sur les fichiers traditionnel) ? ou est ce que l'archive est parcourue pour ne récupérée et charger que le fichier nécessaire.

Existe t'il un système de cache où un fichier récupérée dans l'archive ne sera pas a nouveau désarchivé lors d'un prochain appel (je ne sais pas si je suis très clair)

En tout cas, merci pour ce blog. Je regrette vraiment de ne l'avoir découvert qu'avant hier.

7. Par Pascal MARTIN le 2008-11-20 18:37
Pascal MARTIN

Hello !

Merci :-)

Je suis on ne peut plus d'accord pour l'article sur les performances ^^
Mais avant de pouvoir en rédiger un, il faut que j'expérimente... Peut-être pour dans quelques temps, donc, qui sait ;-)

(Et ça serait l'occasion de voir ce qu'on peut faire comme système d'installation en se basant sur des Phar...)

8. Par Madis le 2009-03-01 01:47
Madis

Si phar a une option de sécurité permettant d'interdire le "dezippage" afin de protéger le code source, alors ça risque d'être très intéressant pour les éditeurs de logiciels en PHP (j'en suis un ^^).
L'équivalent d'un .exe multi-plateforme quoi ;-)

9. Par Pascal MARTIN le 2009-03-01 13:19
Pascal MARTIN

Considérant qu'il faut que la Phar soit "dézippable" nativement par PHP, sans ajouter d'extension spécifique ou quoi que ce soit, j'ai peur que cela soit difficile -- et j'ajouterais que je n'ai pas souvenir d'avoir vu quoi que ce soit à ce sujet dans la doc : ce n'est tout simplement pas l'objectif à l'origine de la mise en place des Phars.

(Et je serais tenté d'ajouter que ce n'est pas vraiment dans la philosophie libre de PHP)

Si vous voulez "protéger" le code de vos applications PHP, vous pouvez toujours jeter un oeil sur des outils comme Zend Guard... Mais je serais tenté de dire que la solution la plus appréciable, et probablement la plus bénéfique à long terme, serait d'avoir des clients avec lesquels vous pouvez développer une relation de confiance !

10. Par Skan le 2012-04-10 19:13
Skan

Hello,

Je teste (enfin) le PHAR et je ne trouve pas sur Internet de réponse à mes envies: est-il possible d'utiliser plusieurs lib au format PHAR pour une application? Un peu comme un JAR peut embarquer plusieurs JAR en lui-même.

11. Par Skan le 2012-04-11 11:29
Skan

Je me réponds à moi-même.
Ce qui me plantait est la conf du module Suhosin. Bien ajouter dedans:
suhosin.executor.include.whitelist="phar"

^^

Ce post n'est pas ouvert aux nouveaux commentaires (probablement parce qu'il a été publié il y a longtemps).