LoginSignup
6
4

More than 5 years have passed since last update.

Pharに自作スタブをセットする

Last updated at Posted at 2014-12-14

Pharの基本的な使い方は

で紹介されていますが、このままだと__HALT_COMPILER();より前のPHPコード領域に挿入されるコードは以下のようなデフォルトのものが適用されてしまいます。

<?php
$web = '000';
if (in_array('phar', stream_get_wrappers()) && class_exists('Phar', 0)) {
    Phar::interceptFileFuncs();
    set_include_path('phar://' . __FILE__ . PATH_SEPARATOR . get_include_path());
    Phar::webPhar(null, $web);
    include 'phar://' . __FILE__ . '/' . Extract_Phar::START;
    return;
}
if (@(isset($_SERVER['REQUEST_URI']) && isset($_SERVER['REQUEST_METHOD']) && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'POST'))) {
    Extract_Phar::go(true);
    $mimes = array(
        'phps' => 2,
        'c' => 'text/plain',
        'cc' => 'text/plain',
        'cpp' => 'text/plain',
        'c++' => 'text/plain',
        'dtd' => 'text/plain',
        'h' => 'text/plain',
        'log' => 'text/plain',
        'rng' => 'text/plain',
        'txt' => 'text/plain',
        'xsd' => 'text/plain',
        'php' => 1,
        'inc' => 1,
        'avi' => 'video/avi',
        'bmp' => 'image/bmp',
        'css' => 'text/css',
        'gif' => 'image/gif',
        'htm' => 'text/html',
        'html' => 'text/html',
        'htmls' => 'text/html',
        'ico' => 'image/x-ico',
        'jpe' => 'image/jpeg',
        'jpg' => 'image/jpeg',
        'jpeg' => 'image/jpeg',
        'js' => 'application/x-javascript',
        'midi' => 'audio/midi',
        'mid' => 'audio/midi',
        'mod' => 'audio/mod',
        'mov' => 'movie/quicktime',
        'mp3' => 'audio/mp3',
        'mpg' => 'video/mpeg',
        'mpeg' => 'video/mpeg',
        'pdf' => 'application/pdf',
        'png' => 'image/png',
        'swf' => 'application/shockwave-flash',
        'tif' => 'image/tiff',
        'tiff' => 'image/tiff',
        'wav' => 'audio/wav',
        'xbm' => 'image/xbm',
        'xml' => 'text/xml',
       );
    header("Cache-Control: no-cache, must-revalidate");
    header("Pragma: no-cache");
    $basename = basename(__FILE__);
    if (!strpos($_SERVER['REQUEST_URI'], $basename)) {
        chdir(Extract_Phar::$temp);
        include $web;
        return;
    }
    $pt = substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], $basename) + strlen($basename));
    if (!$pt || $pt == '/') {
        $pt = $web;
        header('HTTP/1.1 301 Moved Permanently');
        header('Location: ' . $_SERVER['REQUEST_URI'] . '/' . $pt);
        exit;
    }
    $a = realpath(Extract_Phar::$temp . DIRECTORY_SEPARATOR . $pt);
    if (!$a || strlen(dirname($a)) < strlen(Extract_Phar::$temp)) {
        header('HTTP/1.0 404 Not Found');
        echo "<html>\n <head>\n  <title>File Not Found<title>\n </head>\n <body>\n  <h1>404 - File ", $pt, " Not Found</h1>\n </body>\n</html>";
        exit;
    }
    $b = pathinfo($a);
    if (!isset($b['extension'])) {
        header('Content-Type: text/plain');
        header('Content-Length: ' . filesize($a));
        readfile($a);
        exit;
    }
    if (isset($mimes[$b['extension']])) {
        if ($mimes[$b['extension']] === 1) {
            include $a;
            exit;
        }
        if ($mimes[$b['extension']] === 2) {
            highlight_file($a);
            exit;
        }
        header('Content-Type: ' .$mimes[$b['extension']]);
        header('Content-Length: ' . filesize($a));
        readfile($a);
        exit;
    }
}
class Extract_Phar
{
    static $temp;
    static $origdir;
    const GZ = 0x1000;
    const BZ2 = 0x2000;
    const MASK = 0x3000;
    const START = 'index.php';
    const LEN = XXXX;
    static function go($return  = false)
    {
        $fp = fopen(__FILE__, 'rb');
        fseek($fp, self::LEN);
        $L = unpack('V', $a = (binary)fread($fp, 4));
        $m = (binary)'';
        do {
            $read = 8192;
            if ($L[1] - strlen($m) < 8192) {
                $read = $L[1] - strlen($m);
            }
            $last = (binary)fread($fp, $read);
            $m .= $last;
        } while (strlen($last) && strlen($m) < $L[1]);
        if (strlen($m) < $L[1]) {
            die('ERROR: manifest length read was "' . 
                strlen($m) .'" should be "' .
                $L[1] . '"');
        }
        $info = self::_unpack($m);
        $f = $info['c'];
        if ($f & self::GZ) {
            if (!function_exists('gzinflate')) {
                die('Error: zlib extension is not enabled -' .
                    ' gzinflate() function needed for zlib-compressed .phars');
            }
        }
        if ($f & self::BZ2) {
            if (!function_exists('bzdecompress')) {
                die('Error: bzip2 extension is not enabled -' .
                    ' bzdecompress() function needed for bz2-compressed .phars');
            }
        }
        $temp = self::tmpdir();
        if (!$temp || !is_writable($temp)) {
            $sessionpath = session_save_path();
            if (strpos ($sessionpath, ";") !== false)
                $sessionpath = substr ($sessionpath, strpos ($sessionpath, ";")+1);
            if (!file_exists($sessionpath) || !is_dir($sessionpath)) {
                die('Could not locate temporary directory to extract phar');
            }
            $temp = $sessionpath;
        }
        $temp .= '/pharextract/'.basename(__FILE__, '.phar');
        self::$temp = $temp;
        self::$origdir = getcwd();
        @mkdir($temp, 0777, true);
        $temp = realpath($temp);
        if (!file_exists($temp . DIRECTORY_SEPARATOR . md5_file(__FILE__))) {
            self::_removeTmpFiles($temp, getcwd());
            @mkdir($temp, 0777, true);
            @file_put_contents($temp . '/' . md5_file(__FILE__), '');
            foreach ($info['m'] as $path => $file) {
                $a = !file_exists(dirname($temp . '/' . $path));
                @mkdir(dirname($temp . '/' . $path), 0777, true);
                clearstatcache();
                if ($path[strlen($path) - 1] == '/') {
                    @mkdir($temp . '/' . $path, 0777);
                } else {
                    file_put_contents($temp . '/' . $path, self::extractFile($path, $file, $fp));
                    @chmod($temp . '/' . $path, 0666);
                }
            }
        }
        chdir($temp);
        if (!$return) {
            include self::START;
        }
    }
    static function tmpdir()
    {
        if (strpos(PHP_OS, 'WIN') !== false) {
            if ($var = getenv('TMP') ? getenv('TMP') : getenv('TEMP')) {
                return $var;
            }
            if (is_dir('/temp') || mkdir('/temp')) {
                return realpath('/temp');
            }
            return false;
        }
        if ($var = getenv('TMPDIR')) {
            return $var;
        }
        return realpath('/tmp');
    }
    static function _unpack($m)
    {
        $info = unpack('V', substr($m, 0, 4));
        // skip API version, phar flags, alias, metadata
        $l = unpack('V', substr($m, 10, 4));
        $m = substr($m, 14 + $l[1]);
        $s = unpack('V', substr($m, 0, 4));
        $o = 0;
        $start = 4 + $s[1];
        $ret['c'] = 0;
        for ($i = 0; $i < $info[1]; $i++) {
            // length of the file name
            $len = unpack('V', substr($m, $start, 4));
            $start += 4;
            // file name
            $savepath = substr($m, $start, $len[1]);
            $start += $len[1];
            // retrieve manifest data:
            // 0 = size, 1 = timestamp, 2 = compressed size, 3 = crc32, 4 = flags
            // 5 = metadata length
            $ret['m'][$savepath] = array_values(unpack('Va/Vb/Vc/Vd/Ve/Vf', substr($m, $start, 24)));
            $ret['m'][$savepath][3] = sprintf('%u', $ret['m'][$savepath][3]
                & 0xffffffff);
            $ret['m'][$savepath][7] = $o;
            $o += $ret['m'][$savepath][2];
            $start += 24 + $ret['m'][$savepath][5];
            $ret['c'] |= $ret['m'][$savepath][4] & self::MASK;
        }
        return $ret;
    }
    static function extractFile($path, $entry, $fp)
    {
        $data = '';
        $c = $entry[2];
        while ($c) {
            if ($c < 8192) {
                $data .= @fread($fp, $c);
                $c = 0;
            } else {
                $c -= 8192;
                $data .= @fread($fp, 8192);
            }
        }
        if ($entry[4] & self::GZ) {
            $data = gzinflate($data);
        } elseif ($entry[4] & self::BZ2) {
            $data = bzdecompress($data);
        }
        if (strlen($data) != $entry[0]) {
            die("Invalid internal .phar file (size error " . strlen($data) . " != " .
                $stat[7] . ")");
        }
        if ($entry[3] != sprintf("%u", crc32((binary)$data) & 0xffffffff)) {
            die("Invalid internal .phar file (checksum error)");
        }
        return $data;
    }
    static function _removeTmpFiles($temp, $origdir)
    {
        chdir($temp);
        foreach (glob('*') as $f) {
            if (file_exists($f)) {
                is_dir($f) ? @rmdir($f) : @unlink($f);
                if (file_exists($f) && is_dir($f)) {
                    self::_removeTmpFiles($f, getcwd());
                }
            }
        }
        @rmdir($temp);
        clearstatcache();
        chdir($origdir);
    }
}

「どうしてこうなった」 って感じなので、もうちょっときれいにしたいですよね。そこで自作のスタブをセットしてみましょう。実は composer.phar もこの手段を採っています。

src以下にあるPHPファイルを全てbuild/test.pharにまとめる
$pharpath = __DIR__ . '/build/test.phar';
if (is_file($pharpath)) {
    unlink($pharpath);
}
$phar = new \Phar($pharpath, 0, basename($pharpath));
$phar->setStub('<?php
spl_autoload_register(function ($class) {
    $path = "phar://" . __FILE__ . DIRECTORY_SEPARATOR .
             str_replace("\\", DIRECTORY_SEPARATOR, $class) . ".php";
    if (is_file($path)) {
        require $path;
    }
});
__HALT_COMPILER(); ?>
');
$phar->buildFromDirectory(__DIR__ . '/src', '/\.php\z/');

実際にこれを利用するときは(正しくスタブを書いていれば)従来通り以下のコードでOKです。

require 'test.phar';
6
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4