Source for file Creator.php
Documentation is available at Creator.php
* PHP_Archive Class creator (creates .phar)
* Needed for file manipulation
require_once 'System.php';
require_once 'PHP/Archive.php';
* PHP_Archive Class creator (implements .phar)
* PHAR files a singular archive from which an entire application can run.
* To use it, simply package it using {@see PHP_Archive_Creator} and use phar://
* URIs to your includes. i.e. require_once 'phar://config.php' will include config.php
* from the root of the PHAR file.
* Gz code borrowed from the excellent File_Archive package by Vincent Lascaux.
* @copyright Copyright ? David Shafik and Synaptic Media 2004. All rights reserved.
* @author Davey Shafik <davey@synapticmedia.net>
* @author Greg Beaver <cellog@php.net>
* @link http://www.synapticmedia.net Synaptic Media
* @version $Id: Creator.php,v 1.44 2008/05/19 15:16:55 cellog Exp $
* @var string The Archive Filename
* @var string The temporary path to the TAR archive
* @var string Where the TAR archive will be saved
* @var string The phar alias
* @var boolean Whether or not the archive should be compressed
* @var boolean Whether or not to collapse (remove whitespace/comments)
* @var boolean Whether or not a file has been added to the archive
* Used to construct the internal manifest, or listing of files/directories
* Used to save Phar-specific metadata
* Signature type, either PHP_Archive::SHA1 or PHP_Archive::MD5
* A list of custom callbacks that should be used for manipulating file contents
* prior to adding to the phar.
private $_magicRequireCallbacks = array ();
while ($std != ($std = ereg_replace("[^\/:?]+/\.\./", "", $std))) ;
if (strlen($std) > 1 && $std[0 ] == '/') {
* PHP_Archive Constructor
* @param string|false$init_file Init file (file called by default upon PHAR execution).
* if false, none will be called, and execution will return.
* use this option for libraries
* @param string $alias alias name like "go-pear.phar" to be used for opening
* @param string|false$compress Whether to compress the files or not (will cause slowdown!)
* use 'gz' for zlib compression, 'bz2' for bzip2 compression
* @param bool $relyOnPhar if true, then a slim, phar extension-dependent .phar will be
* @param bool $collapse Remove whitespace and comments from PHP_Archive class
public function __construct($init_file = 'index.php', $alias, $compress = false ,
$relyOnPhar = false , $collapse = false )
$this->temp_path = System ::mktemp (array ('-d', 'phr'));
DIRECTORY_SEPARATOR . 'Archive.php');
$contents = self ::collapse ($contents);
$contents = trim(str_replace(array ('<?php', '?>'), array ('', ''), $contents));
// make sure .phars added to CVS don't get checksum errors because of CVS tags
$contents = str_replace('* @version $Id', '* @version Id', $contents);
if (function_exists('mb_internal_encoding')) {
mb_internal_encoding('ASCII');
// for smooth use of phar extension
$unpack_code .= "if (!class_exists('PHP_Archive')) {";
$unpack_code .= $contents;
if (!class_exists('Phar')) {
PHP_Archive::mapPhar(null, __COMPILER_HALT_OFFSET__);
} catch (Exception \$e) {
if (class_exists('PHP_Archive') && !in_array('phar', stream_get_wrappers())) {
stream_wrapper_register('phar', 'PHP_Archive');
$unpack_code .= "if (!extension_loaded('phar')) {";
$unpack_code .= 'die("Error - phar extension not loaded");
} catch (Exception \$e) {
$unpack_code .= "\n@ini_set('memory_limit', -1);\n";
require_once \'phar://@ALIAS@/' . addslashes($init_file) . '\';
$unpack_code .= '__HALT_COMPILER();';
* Set meta-data for entire Phar archive
$this->_metadata = $metadata;
* Append a signature to this phar when it is created
* Append a signature to this phar when it is created
* Specify a custom "magic require" callback for processing file contents.
* This will be called regardless of the magicrequire parameter's
* value for {@link addString()} or {@link addFile()}
* @param callback $callback
$this->_magicRequireCallbacks[] = $callback;
* Add a file to the PHP Archive
* @param string $file Path of the File to add
* @param string $save_path The save location of the file in the archive
* @param false $magicrequire unused, set this to false
* @param mixed $metadata Any file-specific metadata to save
public function addFile($file, $save_path, $magicrequire = false , $metadata = null )
* For web-based applications, construct a default front controller
* that will direct to the correct file within the phar.
* @param string $indexfile relative path to startup index file (defaults to the same file as
* is used for CLI startup)
* @param array $defaultmimes list of MIME types to use, associative array of
* extension => mime type. default is
* from {@link PHP_Archive::$defaultmimes}
* @param array $defaultphp list of file extensions that should be parsed as PHP. default is
* from {@link PHP_Archive::$defaultphp}
* @param array $defaultphps list of file extensions that should be parsed as PHP source. default is
* from {@link PHP_Archive::$defaultphps}
* @param array $deny list of files that should be hidden (return a 404 response)
* Each file should be a valid pcre regular expression like '/.+\.inc$/',
* which will deny all .inc files from being served. The default is
* from {@link PHP_Archive::$deny}
$defaultphps = false , $deny = false )
$defaultphp = PHP_Archive ::$defaultphp;
$defaultphps = PHP_Archive ::$defaultphps;
$deny = PHP_Archive ::$deny;
$templatefile = '@data_dir@/PHP_Archive/data/phar_frontcontroller.tpl';
$template = file_get_contents ($templatefile);
$contents = str_replace("@ini_set('memory_limit', -1);",
"@ini_set('memory_limit', -1);\n" . $template . "\n");
if ($defaultmimes || $defaultphp || $defaultphps || $deny) {
$extra .= "\nPHP_Archive::\$defaultmimes = " .
$extra .= "\nPHP_Archive::\$defaultphp = " .
$extra .= "\nPHP_Archive::\$defaultphps = " .
$extra .= "\nPHP_Archive::\$deny = " .
// if Phar extension is present, use the template code instead
$defaultphp = PHP_Archive ::$defaultphp;
$defaultphps = PHP_Archive ::$defaultphps;
$deny = PHP_Archive ::$deny;
$templatefile = '@data_dir@/PHP_Archive/data/phar_frontcontroller.tpl';
$template = file_get_contents ($templatefile);
$template = str_replace('@initfile@', $indexfile, $template);
$contents = str_replace("@ini_set('memory_limit', -1);",
"@ini_set('memory_limit', -1);\n" .
'if (extension_loaded(\'phar\')) {' . $template . '} else {' .
"if (!empty(\$_SERVER['REQUEST_URI'])) " .
"{PHP_Archive::webFrontController('" .
addslashes($indexfile) . "');exit;}}\n", $contents);
* Add a string to the PHP Archive as a file
* @param string $file_contents Contents of the File to add
* @param string $save_path The save location of the file in the archive
public function addString($file_contents, $save_path, $magicrequire = false ,
$save_path = self ::processFile ($save_path);
if (count($this->_magicRequireCallbacks)) {
foreach ($this->_magicRequireCallbacks as $callback) {
$file_contents = call_user_func($callback, $file_contents, $save_path);
die ('ERROR: magicrequire is removed, set a magicrequire callback to ' .
'array("PHP_Archive_Creator", "simpleMagicRequire) to implement');
$size = strlen($file_contents);
$crc32 = crc32($file_contents);
// save crc32 of file and the uncompressed file size, so we
// can do a sanity check on the file when opening it from the phar
$file_contents = gzdeflate($file_contents, 9 );
$file_contents = bzcompress ($file_contents, 9 );
System ::mkdir (array ('-p', dirname($this->temp_path . DIRECTORY_SEPARATOR . 'contents' .
DIRECTORY_SEPARATOR . $save_path)));
DIRECTORY_SEPARATOR . $save_path)) {
die ('ERROR: path "' . $save_path . '" already exists');
DIRECTORY_SEPARATOR . $save_path, $file_contents);
$flags |= ($this->compress == 'gz') ? 0x00001000 : 0x00002000;
$flags |= 0555; // file permissions
'tempfile' => $this->temp_path . DIRECTORY_SEPARATOR . 'contents' .
DIRECTORY_SEPARATOR . $save_path,
'actualsize' => strlen($file_contents),
'metadata' => $metadata);
$this->_magicRequireCallbacks = array ();
* prepends all include/require calls with "phar://alias"
* @param string $contents file contents
* @param string $path internal path of the file
$file_contents = str_replace("require_once '", "require_once 'phar://" . $this->alias . "/",
$file_contents = str_replace("include_once '", "include_once 'phar://" . $this->alias . "/",
* prepends "phar://alias" only to include/require that use quotes
* @param string $contents file contents
* @param string $path internal path of the file
'/(include|require)(_once)?\s*((?:\'|")[^;]+);/',
'$1$2 \'phar://' . $this->alias . '/\' . $3;', $contents);
* The basic magicrequire callback (implements old-fashioned magicrequire)
* @param string $contents file contents
* @param string $path internal path of the file
'/(include|require)(_once)?([^;]+);/',
'$1$2 \'phar://' . $this->alias . '/\' . $3', $contents);
if (!isset ($info['extension'])) {
if ($info['extension'] != 'php') {
$alias = '\'phar://' . $this->alias . '/\' . ';
for ($i = 0; $i < count($contents); $i++ ) {
if ($token[0 ] == T_OPEN_TAG ) {
if ($token[0 ] == T_CLOSE_TAG ) {
if ($token[0 ] != T_INCLUDE && $token[0 ] != T_INCLUDE_ONCE &&
$token[0 ] != T_REQUIRE && $token[0 ] != T_REQUIRE_ONCE ) {
$token = $contents[++ $i];
if ($token == '(' || $token == '{' || $token == '"' ||
$finished .= ' ' . $alias . $token;
// we have some oddness, don't touch this with a 10-foot pole
if ($token[0 ] == T_WHITESPACE ) {
$finished .= $alias . $token[1 ];
* Tell whether to ignore a file or a directory
* allows * and ? wildcards
* @param string $file just the file name of the file or directory,
* in the case of directories this is the last dir
* @param string $path the full path
* @param 1|0 $return value to return if regexp matches. Set this to
* false to include only matches, true to exclude
* @return bool true if $path should be ignored, false if it should not
private function _checkIgnore ($file, $path, $return = 1 )
foreach($this->ignore[$return] as $match) {
// match is an array if the ignore parameter was a /path/to/pattern
// check to see if the path matches with a path delimiter appended
// check to see if it matches without an appended path delimiter
// check to see if the file matches the file portion of the regex string
// check to see if the full path matches the regex
strtoupper($path . DIRECTORY_SEPARATOR . $file), $find);
// ignore parameter was just a pattern with no path delimiters
// check it against the path
// check it against the file only
* Construct the {@link $ignore} array
* @param array strings of files/paths/wildcards to ignore
* @param 0 |10 = files to include, 1 = files to ignore
private function _setupIgnore ($ignore, $index)
for($i=0; $i< count($ignore); $i++ ) {
$ignore[$i] = strtr($ignore[$i], "\\", "/");
if (!empty ($ignore[$i])) {
$ig[] = $this->_getRegExpableSearchString ($ignore[$i]);
if (basename($ignore[$i]) . '/' == $ignore[$i]) {
$ig[] = $this->_getRegExpableSearchString ($ignore[$i]);
$ig[] = array ($this->_getRegExpableSearchString ($ignore[$i]),
$this->_getRegExpableSearchString (basename($ignore[$i])));
$this->ignore[$index] = $ig;
$this->ignore[$index] = false;
} else $this->ignore[$index] = false;
* Converts $s into a string that can be used with preg_match
* @param string $s string with wildcards ? and *
* @return string converts * to .*, ? to ., etc.
private function _getRegExpableSearchString ($s)
if (DIRECTORY_SEPARATOR == '\\') {
$x = strtr($s, array ('?' => '.','*' => '.*','.' => '\\.','\\' => '\\\\','/' => '\\/',
'[' => '\\[',']' => '\\]','-' => '\\-'));
if (strpos($s, DIRECTORY_SEPARATOR ) !== false &&
$x = " (?:.*$y$x?.*|$x.*)";
* Test whether an entry should be processed.
* Normally, it ignores all files and directories that begin with "." addhiddenfiles option
* instead only ignores "." and ".." entries
* @param string directory name of entry
private function _testFile ($directory, $entry)
return is_file($directory . '/' . $entry) ||
(is_dir($directory . '/' . $entry) && !in_array($entry, array ('.', '..')));
* Retrieve a listing of every file in $directory and
* The return format is an array of full paths to files
* @return array list of files in a directory
* @param string $directory full path to the directory you want the list of
public function dirList($directory, $toplevel = null )
if ($toplevel === null ) {
$dirname = str_replace($toplevel . DIRECTORY_SEPARATOR , '', $directory);
$dirname .= DIRECTORY_SEPARATOR;
while ($d && false !== ($entry = $d->read ())) {
if ($this->_testFile ($directory, $entry)) {
if (is_file($directory . '/' . $entry)) {
// if include option was set, then only pass included files
if ($this->_checkIgnore ($entry, $directory . '/' . $entry, 0 )) {
// if ignore option was set, then only pass included files
if ($this->_checkIgnore ($entry, $directory . '/' . $entry, 1 )) {
$ret[$directory . DIRECTORY_SEPARATOR . $entry] = $dirname . $entry;
if (is_dir($directory . '/' . $entry)) {
$tmp = $this->dirList($directory . DIRECTORY_SEPARATOR . $entry, $toplevel);
foreach($tmp as $i => $ent) {
* Add a directory to the archive
* @param string The directory path to add
* @param array files to ignore
* @param array files to include (all others ignored)
* @param bool If set, then "require_once '" will be replaced with
* "require_once 'phar://$magicrequire/" [deprecated]
* @param string Directory to consider as the top-level directory
public function addDir($dir, $ignore = array (), $include = array (), $magicrequire = false ,
$this->_setupIgnore ($ignore, 1 );
$this->_setupIgnore ($include, 0 );
$list = $this->dirList($dir, $toplevel);
return $this->addArray($list, $magicrequire);
* Collapse a block of code (remove whitespace and comments)
* @param string $contents PHP code to collapse
* @author Sean Coates <sean@php.net>
private static function collapse ($contents)
foreach ($tokens as $t) {
list ($token, $data) = $t;
if ($token == T_WHITESPACE ) {
if (strpos($data, "\n") !== false ) {
} elseif ($token == T_COMMENT || $token == T_DOC_COMMENT ) {
* add an array of files to the archive
* @param unknown_type $files
* @param bool $magicrequire determines whether to attempt to replace all
* calls to require or include with internal
public function addArray($files, $magicrequire = false )
if (!is_array($files) || empty ($files)) {
foreach ($files as $file_path => $save_path) {
$returns[] = $this->addFile($file_path, $save_path, $magicrequire);
* Construct the .phar and save it
* @param string $file_path
* @return bool success of operation
$newfile = fopen($file_path, 'wb');
if (isset ($this->alias)) {
strlen('__COMPILER_HALT_OFFSET__')), $loader);
// relative offset from end of manifest
foreach ($this->manifest as $savepath => $info) {
$size = $info['originalsize'];
$manifest[$savepath] = array (
$size, // original size = 0
$info['actualsize'], // actual size in the .phar = 2
$info['crc32'], // crc32 = 3
$info['flags'], // flags = 4
$info['metadata']); // metadata = 5
$offset += $info['actualsize'];
foreach ($this->manifest as $savepath => $info) {
$file = fopen($info['tempfile'], 'rb');
$fp = fopen($file_path, 'ab');
// add signature indicator plus the magic indicator
// ah to be immortalized in file format
* serialize the manifest in a C-friendly way
* @param array $manifest An array like so:
* $size, // original size = 0
* time(), // save time = 1
* $offset, // offset from start of files = 2
* $info['actualsize']); // actual size in the .phar = 3
$apiver = explode('.', '@API-VER@');
// store API version and compression in 2 bytes
$apiver = chr(((int) ((int) $apiver[0 ]) << 4 ) + ((int) $apiver[1 ])) .
chr((int) ($apiver[2 ] << 4 ) + ($this->compress ? 0x1 : 0 ));
foreach ($manifest as $savepath => $info) {
// save the string length, then the string, then this info
// uncompressed file size
if ($metadata === null ) {
// save the size of the manifest
Documentation generated on Mon, 19 May 2008 11:30:12 -0400 by phpDocumentor 1.4.0. PEAR Logo Copyright © PHP Group 2004.
|