diff --git a/src/Application.php b/src/Application.php index 22c363d..1b06e84 100644 --- a/src/Application.php +++ b/src/Application.php @@ -134,6 +134,9 @@ private function runSaver($code) { if ($aliasName = $this->config->getPharAliasName()) { $pharBuilder->setAliasName($aliasName); } + if ($bootstrapName = $this->config->getPharBootstrapName()) { + $pharBuilder->setBootstrapName($bootstrapName); + } if ($this->config->hasPharHashAlgorithm()) { $pharBuilder->setSignatureType($this->config->getPharHashAlgorithm()); } diff --git a/src/AutoloadRenderer.php b/src/AutoloadRenderer.php index 9b1fa30..b6ab2c9 100644 --- a/src/AutoloadRenderer.php +++ b/src/AutoloadRenderer.php @@ -94,6 +94,20 @@ class AutoloadRenderer { */ protected $variables = array(); + /** + * Templates head + * + * @var string + */ + protected $head = "variables['___'.$name.'___'] = $value; } + /** + * Set head of the template code. + * + * @param string|string[] $head One or more filenames or code fragments to be included at the beginning of the template + */ + public function setHead($head) { + $heads = (array) $head; + foreach ($heads as &$fileOrCode) + { + if (file_exists($fileOrCode)) { + $fileOrCode = file_get_contents($fileOrCode); + } + } + unset($fileOrCode); + $this->head = implode("\n;\n",$heads); + if (!preg_match('@<\?php\s@',$this->head)) { + $this->head = "head; + } + } + + /** + * Set tail of the template code. + * + * @param string|string[] $tail One or more filenames or code fragments to be included at the end of the + * template + * + * @throws \TheSeer\Autoload\AutoloadBuilderException + */ + public function setTail($tail) { + $tail = (array) $tail; + foreach ( $tail as &$fileOrCode) + { + if (file_exists($fileOrCode)) { + $fileOrCode = file_get_contents($fileOrCode); + $fileOrCode = preg_replace('@^(#!/.*?[\r\n]+)?<\?php\s@', '', $fileOrCode); + } + } + unset($fileOrCode); + $this->tail = implode("\n;\n",$tail); + $test = preg_replace('@\?>.*?<\?php\s@', '', $this->tail); + if (preg_match('@<\?php\s@', $test)) { + throw new AutoloadBuilderException("Template tail includes unmatched 'variables, array( - '___CREATED___' => date( $this->dateformat, $this->timestamp ? $this->timestamp : time()), - '___CLASSLIST___' => join( ',' . $this->linebreak . $this->indent, $entries), + '___CREATED___' => date( $this->dateformat, $this->timestamp ?: time()), + '___CLASSLIST___' => implode( ',' . $this->linebreak . $this->indent, $entries), '___BASEDIR___' => $baseDir, '___AUTOLOAD___' => 'autoload' . md5(serialize($entries)), '___EXCEPTION___' => $this->throwExceptions ? 'true' : 'false', - '___PREPEND___' => $this->usePrepend ? 'true' : 'false' + '___PREPEND___' => $this->usePrepend ? 'true' : 'false', + '___HEAD___' => $this->head, + '___TAIL___' => $this->tail )); - return str_replace(array_keys($replace), array_values($replace), $template); + + do { + $template = str_replace(array_keys($replace), array_values($replace), $template, $count); + } while ($count); + + return $template; } } @@ -291,6 +356,7 @@ class AutoloadBuilderException extends \Exception { const TemplateNotFound = 1; const InvalidTimestamp = 2; + const TemplateIncludesPhpOpener = 3; } diff --git a/src/CLI.php b/src/CLI.php index 21d9c81..4b47e39 100644 --- a/src/CLI.php +++ b/src/CLI.php @@ -164,7 +164,8 @@ private function configure(array $env, \ezcConsoleInput $input) { $compression, $input->getOption('all')->value, $input->getOption('key')->value, - $input->getOption('alias')->value + $input->getOption('alias')->value, + $input->getOption('bootstrap')->value ); $config->setVariable('PHAR', $input->getOption('alias')->value ? $input->getOption('alias')->value : basename($output) @@ -241,6 +242,12 @@ private function configure(array $env, \ezcConsoleInput $input) { if ($template = $input->getOption('template')->value) { $config->setTemplate($template); } + if ($head = $input->getOption('head')->value) { + $config->setHead($head); + } + if ($tail = $input->getOption('tail')->value) { + $config->setTail($tail); + } if ($linebreak = $input->getOption('linebreak')->value) { $config->setLinebreak($linebreak); } @@ -298,10 +305,16 @@ protected function showUsage() { -b, --basedir Basedir for filepaths -t, --template Path to code template to use + -H, --head Path to file or code to include at the beginning of the file (must include " + Save bootstrap code (stub) also in within the phar file. If -o points to anything + other than a .phar file (thus creating a PharData archive), this is required, defaulting + to 'autoload.php' if omitted (requires -p) --all Include all files in given directory when creating a phar --alias Specify explicit internal phar alias filename (default: output filename) --hash Force given hash algorithm (SHA-1, SHA-256 or SHA-512) (requires -p, conflicts with --key) @@ -378,6 +391,13 @@ protected function setupInput() { array( new \ezcConsoleOptionRule( $input->getOption( 'o' ) ) ) )); + $input->registerOption( new \ezcConsoleOption( + '', 'bootstrap', \ezcConsoleInput::TYPE_STRING, NULL, FALSE, + 'Filename within the phar, if a PharData is created (output does not end on .phar, but .tar, .tar.gz, tar.bz)', + NULL, + array( new \ezcConsoleOptionRule( $input->getOption( 'p' ) ) ) + )); + $input->registerOption( new \ezcConsoleOption( '', 'all', \ezcConsoleInput::TYPE_NONE, NULL, FALSE, 'Add all files from src dir to phar', @@ -453,6 +473,16 @@ protected function setupInput() { 'Path to code template to use' )); + $input->registerOption( new \ezcConsoleOption( + 'H', 'head', \ezcConsoleInput::TYPE_STRING, NULL, TRUE, + 'Path to file or code to include at the beginning of the file (must include "registerOption( new \ezcConsoleOption( + 'T', 'tail', \ezcConsoleInput::TYPE_STRING, NULL, TRUE, + 'Path to file or code to include at the end of the file ("registerOption( new \ezcConsoleOption( '', 'follow', \ezcConsoleInput::TYPE_NONE, NULL, FALSE, 'Enables following symbolic links', @@ -589,7 +619,7 @@ private function preBootstrap() { } if (count($missing)) { throw new CLIEnvironmentException( - join("\n", $missing), + implode("\n", $missing), CLIEnvironmentException::ExtensionMissing ); } diff --git a/src/Config.php b/src/Config.php index 61d935b..27b2de2 100644 --- a/src/Config.php +++ b/src/Config.php @@ -49,6 +49,8 @@ class Config { private $blacklist = array(); private $baseDirectory = NULL; private $template; + private $head = ''; + private $tail = ''; private $linebreak = "\n"; private $indent; private $lint = FALSE; @@ -67,6 +69,7 @@ class Config { private $pharKey; private $pharAll = false; private $pharAliasName = ''; + private $pharBootstrapName = ''; private $pharHashAlgorithm; private $followSymlinks = false; private $cacheFilename; @@ -88,7 +91,7 @@ public function setHomeDirectory($homeDir) { $this->homeDirectory = $homeDir; } - public function getHomeDirectory(): string { + public function getHomeDirectory() { return $this->homeDirectory; } @@ -100,7 +103,7 @@ public function getBaseDirectory() { $comparator = new PathComparator($this->directories); return $comparator->getCommonBase(); } - if ($this->outputFile != 'STDOUT') { + if ($this->outputFile !== 'STDOUT') { return realpath(dirname($this->outputFile) ?: '.'); } $tmp = $this->getDirectories(); @@ -229,12 +232,13 @@ public function getOutputFile() { return $this->outputFile; } - public function enablePharMode($compression = 'NONE', $all = true, $key = NULL, $alias = NULL) { + public function enablePharMode($compression = 'NONE', $all = true, $key = NULL, $alias = NULL, $bootstrap = NULL) { $this->pharMode = true; $this->pharCompression = $compression; $this->pharAll = (boolean)$all; $this->pharKey = $key; $this->pharAliasName = $alias; + $this->pharBootstrapName = $bootstrap; } public function isPharMode() { @@ -257,6 +261,10 @@ public function getPharAliasName() { return $this->pharAliasName; } + public function getPharBootstrapName() { + return $this->pharBootstrapName; + } + public function hasPharHashAlgorithm() { return $this->pharHashAlgorithm !== null; } @@ -361,6 +369,22 @@ public function getTemplate() { } + public function setHead($head) { + $this->head = $head; + } + + public function getHead() { + return $this->head; + } + + public function setTail($tail) { + $this->tail = $tail; + } + + public function getTail() { + return $this->tail; + } + public function setTolerantMode($tolerant) { $this->tolerant = (boolean)$tolerant; } @@ -400,7 +424,7 @@ public function isQuietMode() { public function getDirectories() { $list = array(); foreach($this->directories as $dir) { - if (is_file($dir) && basename($dir) == 'composer.json') { + if (is_file($dir) && basename($dir) === 'composer.json') { foreach(new ComposerIterator(new \SplFileInfo($dir), $this->getHomeDirectory()) as $d) { $list[] = $d; } diff --git a/src/Factory.php b/src/Factory.php index 746500c..93c57ee 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -163,7 +163,7 @@ public function getPharBuilder() { * * @param CollectorResult $result * - * @throws \RuntimeException + * @throws \RuntimeException|\TheSeer\Autoload\AutoloadBuilderException * @return \TheSeer\Autoload\AutoloadRenderer|\TheSeer\Autoload\StaticRenderer */ public function getRenderer(CollectorResult $result) { @@ -218,6 +218,14 @@ public function getRenderer(CollectorResult $result) { $renderer->setVariable($name, $value); } + if($head = $this->config->getHead()) { + $renderer->setHead($head); + } + + if($tail = $this->config->getTail()) { + $renderer->setTail($tail); + } + return $renderer; } diff --git a/src/PharBuilder.php b/src/PharBuilder.php index adbaacf..afca55d 100644 --- a/src/PharBuilder.php +++ b/src/PharBuilder.php @@ -46,6 +46,7 @@ class PharBuilder { private $key; private $basedir; private $aliasName; + private $bootstrapName; private $signatureType; private $directories = array(); @@ -66,7 +67,7 @@ public function setCompressionMode($mode) { } public function setSignatureType($type) { - if (!in_array($type, array_keys($this->supportedSignatureTypes))) { + if (!array_key_exists($type, $this->supportedSignatureTypes)) { throw new \InvalidArgumentException( sprintf('Signature type "%s" not known or not supported by this PHP installation.', $type) ); @@ -86,13 +87,27 @@ public function setAliasName($name) { $this->aliasName = $name; } + public function setBootstrapName($name) { + $this->bootstrapName = $name; + } + public function build($filename, $stub) { if (file_exists($filename)) { unlink($filename); } - $phar = new \Phar($filename, 0, $this->aliasName != '' ? $this->aliasName : basename($filename)); + $filename = new \SplFileInfo($filename); + $class = $filename->getExtension() === 'phar' ? \Phar::class : \PharData::class; + $phar = new $class($filename, 0, $this->aliasName ?: basename($filename)); $phar->startBuffering(); - $phar->setStub($stub); + if ('\\Phar' === $class) { + $phar->setStub($stub); + } elseif (empty($this->bootstrapName)) { + $this->bootstrapName = 'autoload.php'; + } + if ($this->bootstrapName) { + $phar[$this->bootstrapName] = $stub; + $phar->setMetadata(array('bootstrap' => $this->bootstrapName)); + } if ($this->key !== NULL) { $privateKey = ''; openssl_pkey_export($this->key, $privateKey); @@ -103,9 +118,15 @@ public function build($filename, $stub) { $phar->setSignatureAlgorithm($this->selectSignatureType()); } - $basedir = $this->basedir ? $this->basedir : $this->directories[0]; + $basedir = $this->basedir ?: $this->directories[0]; foreach($this->directories as $directory) { - $phar->buildFromIterator($this->scanner->__invoke($directory), $basedir); + if (file_exists("$directory/")) { + $phar->buildFromIterator($this->scanner->__invoke($directory), $basedir); + } else { + $comparator = new PathComparator(array($basedir, $directory)); + $localName = str_replace($comparator->getCommonBase().'/', '', $directory); + $phar->addFile($directory, $localName); + } } if ($this->compression !== \Phar::NONE) { @@ -120,7 +141,7 @@ private function selectSignatureType() { } $supported = \Phar::getSupportedSignatures(); foreach($this->supportedSignatureTypes as $candidate => $type) { - if (in_array($candidate, $supported)) { + if (in_array($candidate, $supported, true)) { return $type; } } diff --git a/src/StaticRenderer.php b/src/StaticRenderer.php index db65d4d..6dc77fd 100644 --- a/src/StaticRenderer.php +++ b/src/StaticRenderer.php @@ -109,11 +109,17 @@ public function render($template) { '___CREATED___' => date( $this->dateformat, $this->timestamp ? $this->timestamp : time()), '___FILELIST___' => $this->renderHelper->render($entries), '___BASEDIR___' => $baseDir, - '___AUTOLOAD___' => uniqid('autoload', true) + '___AUTOLOAD___' => uniqid('autoload', true), + '___HEAD___' => $this->head, + '___TAIL___' => $this->tail ) ); - return str_replace(array_keys($replace), array_values($replace), $template); + do { + $template = str_replace(array_keys($replace), array_values($replace), $template, $count); + } while ($count); + + return $template; } /** diff --git a/src/templates/ci/default.php.tpl b/src/templates/ci/default.php.tpl index b007e0b..33cd126 100644 --- a/src/templates/ci/default.php.tpl +++ b/src/templates/ci/default.php.tpl @@ -1,4 +1,4 @@ -