Phar アーカイブの使用法: 導入

Phar アーカイブは Java の JAR アーカイブと似た概念のものですが、 PHP アプリケーションで使用する際に必要な機能をより柔軟に使用できるよう改良しています。 Phar アーカイブを使用すると、PHP アプリケーションやライブラリを ひとつのファイルにまとめて配布できるようになります。 Phar アーカイブ形式のアプリケーションは、その他の PHP アプリケーションとまったく同様に扱えます。

php coolapplication.phar
  

Phar アーカイブ形式のライブラリを使用する方法も、 その他の通常の PHP ライブラリとまったく同じです。

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

phar ストリームラッパーは phar 拡張モジュールの要となるものです。 詳細については こちら で説明します。このストリームラッパーを使用すると、 phar 内の個々のファイルに対して fopenopendir といった標準のファイル操作関数を使用でき、 まるで通常のファイルシステム上にあるのと同じような感覚でアクセスできます。 phar ストリームラッパーは、 ファイルやディレクトリに対する読み書き操作をすべてサポートしています。

<?php
include 'phar://coollibrary.phar/internal/file.php';
header('Content-type: image/jpeg');
// phar にアクセスするには、フルパスあるいはエイリアスを使用します
echo file_get_contents('phar:///fullpath/to/coollibrary.phar/images/wow.jpg');
?>

Phar クラスでは、 ファイルへのアクセスや phar アーカイブ作成用により高度な機能も実装しています。 Phar クラスについての説明は こちら をご覧ください。

<?php
try {
    // 既存の phar をオープンします
    $p = new Phar('coollibrary.phar', 0);
    // Phar は SPL の DirectoryIterator クラスを継承しています
    foreach (new RecursiveIteratorIterator($p) as $file) {
        // $file は PharFileInfo クラスで、これは SplFileInfo を継承しています
        echo $file->getFileName() . "\n";
        echo file_get_contents($file->getPathName()) . "\n"; // 内容を表示します
    }
    if (isset($p['internal/file.php'])) {
        var_dump($p['internal/file.php']->getMetadata());
    }

    // 新しい phar の作成 - php.ini で phar.readonly を 0 にしておく必要があります。
    // phar.readonly は、セキュリティ上の理由によってデフォルトで有効になっています。
    // 実際の運用サーバーでは、決して Phar を作成する必要はないはずで、
    // 単に実行できるだけでよいはずです。
    if (Phar::canWrite()) {
        $p = new Phar('newphar.tar.phar', 0, 'newphar.tar.phar');
        // tar 形式の phar アーカイブを作成し、gzip で圧縮します (.tar.gz)
        $p = $p->convertToExecutable(Phar::TAR, Phar::GZ);

        // トランザクションの作成 - newphar.phar には、
        // stopBuffering() がコールされるまで (一時的なストレージが必要となっても) なにも書き込まれません
        $p->startBuffering();
        // すべてのファイルを /path/to/project に追加し、プレフィックス "project" で phar に保存します
        $p->buildFromIterator(new RecursiveIteratorIterator(new RecursiveDirectoryIterator('/path/to/project')), '/path/to/');

        // 配列形式の API で新しいファイルを追加します
        $p['file1.txt'] = 'Information';
        $fp = fopen('hugefile.dat', 'rb');
        // すべてのデータをストリームからコピーします
        $p['data/hugefile.dat'] = $fp;

        if (Phar::canCompress(Phar::GZ)) {
            $p['data/hugefile.dat']->compress(Phar::GZ);
        }

        $p['images/wow.jpg'] = file_get_contents('images/wow.jpg');
        // ファイルのメタデータには任意の値を保存できます
        $p['images/wow.jpg']->setMetadata(array('mime-type' => 'image/jpeg'));
        $p['index.php'] = file_get_contents('index.php');
        $p->setMetadata(array('bootstrap' => 'index.php'));

        // phar アーカイブをディスクに保存します
        $p->stopBuffering();
    }
} catch (Exception $e) {
    echo 'Phar をオープンできません: ', $e;
}
?>

さらに、phar ファイルの中身の検証を行うこともできます。対称型ハッシュアルゴリズム (MD5、SHA1、SHA256、そしてもし ext/hash が有効ならさらに SHA512) のいずれか、 あるいは OpenSSL を使用した非対称公開鍵/秘密鍵ペアによる署名のいずれかを使用します。 OpenSSL による署名を使用するには、 まず公開鍵/秘密鍵のペアを作成し、秘密鍵を使用して Phar::setSignatureAlgorithm で署名を行います。 公開キーによる展開はこのようなコードで行います。

<?php
$public = openssl_get_publickey(file_get_contents('private.pem'));
$pkey = '';
openssl_pkey_export($public, $pkey);
?>
公開鍵は phar アーカイブと同じ場所、名前でなければなりません。つまり、phar アーカイブの保存先が /path/to/my.phar の場合は公開鍵を /path/to/my.phar.pubkey としなければなりません。 そうしないと、OpenSSL 署名の検証ができなくなります。

Phar には 3 つのstaticメソッド Phar::webPharPhar::mungServer そして Phar::interceptFileFuncs が追加されました。これは、通常のファイルシステム用あるいはウェブアプリケーション用の PHP アプリケーションのパッケージを作成する際に重要となります。 Phar::webPhar は、HTTP のコールを phar アーカイブ内の特定の場所に転送するフロントコントローラを実装します。 Phar::mungServer$_SERVER 配列の中身をうまく変更して アプリケーションでその値を使えるようにします。 Phar::interceptFileFuncs は、 fopenfile_get_contentsopendir そしてすべての stat 系関数 (file_existsis_readable など) のコールを Phar が横取りして、相対パスを phar アーカイブ内の適切な位置に変換するようにします。

たとえば、あの有名な phpMyAdmin を phar アーカイブにすることを考えてみましょう。 必要なのは、こんな単純なスクリプトだけです。あとは、 ユーザー名とパスワードを変更してウェブサーバー上から phpMyAdmin.phar.tar.php にアクセスするだけで利用できます。

<?php
@unlink('phpMyAdmin.phar.tar.php');
copy('phpMyAdmin-2.11.3-english.tar.gz', 'phpMyAdmin.phar.tar.php');
$a = new Phar('phpMyAdmin.phar.tar.php');
$a->startBuffering();
$a["phpMyAdmin-2.11.3-english/config.inc.php"] = '<?php
/* サーバーの設定 */
$i = 0;

/* Server localhost (config:root) [1] */
$i++;
$cfg[\'Servers\'][$i][\'host\'] = \'localhost\';
$cfg[\'Servers\'][$i][\'extension\'] = \'mysqli\';
$cfg[\'Servers\'][$i][\'connect_type\'] = \'tcp\';
$cfg[\'Servers\'][$i][\'compress\'] = false;
$cfg[\'Servers\'][$i][\'auth_type\'] = \'config\';
$cfg[\'Servers\'][$i][\'user\'] = \'root\';
$cfg[\'Servers\'][$i][\'password\'] = \'\';


/* サーバーの設定はここまで */
if (strpos(PHP_OS, \'WIN\') !== false) {
    $cfg[\'UploadDir\'] = getcwd();
} else {
    $cfg[\'UploadDir\'] = \'/tmp/pharphpmyadmin\';
    @mkdir(\'/tmp/pharphpmyadmin\');
    @chmod(\'/tmp/pharphpmyadmin\', 0777);
}';
$a->setStub('<?php
Phar::interceptFileFuncs();
Phar::webPhar("phpMyAdmin.phar", "phpMyAdmin-2.11.3-english/index.php");
echo "phpMyAdmin is intended to be executed from a web browser\n";
exit -1;
__HALT_COMPILER();
');
$a->stopBuffering();
?>