How to use the default PHP to execute the phar package?

When developing an installation program recently, it was packaged as a phar package. One problem encountered was that the packaged phar package could not be executed without php.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# normal operation
php install.phar
# Report an error
./install.phar
# normal operation php install.phar # Report an error ./install.phar
# normal operation
php install.phar

# Report an error
./install.phar

Omit php to report an error when running:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ ./install.phar
./install.phar: line 1: ?php: No such file or directory
./install.phar: line 3: =: command not found
./install.phar: line 5: syntax error near unexpected token `'phar','
./install.phar: line 5: `if (in_array('phar', stream_get_wrappers()) && class_exists('Phar', 0)) {'
$ ./install.phar ./install.phar: line 1: ?php: No such file or directory ./install.phar: line 3: =: command not found ./install.phar: line 5: syntax error near unexpected token `'phar',' ./install.phar: line 5: `if (in_array('phar', stream_get_wrappers()) && class_exists('Phar', 0)) {'
$ ./install.phar
./install.phar: line 1: ?php: No such file or directory
./install.phar: line 3: =: command not found
./install.phar: line 5: syntax error near unexpected token `'phar','
./install.phar: line 5: `if (in_array('phar', stream_get_wrappers()) && class_exists('Phar', 0)) {'

I don’t know the specific reason at this point, because there is no problem with packaging according to the normal phar process, but it is true that the operation cannot be omitted.

I searched the Internet and there was no specific answer. I thought that composer could be run without php, so I checked the source code of composer.

I found a compile method, found that there was a setStub operation in the code, and called getStub():

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
private function getStub()
{
$stub = <<<'EOF'
#!/usr/bin/env php
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view
* the license that is located at the bottom of this file.
*/
// Avoid APC causing random fatal errors per https://github.com/composer/composer/issues/264
if (extension_loaded('apc') && filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN) && filter_var(ini_get('apc.cache_by_default'), FILTER_VALIDATE_BOOLEAN)) {
if (version_compare(phpversion('apc'), '3.0.12', '>=')) {
ini_set('apc.cache_by_default', 0);
} else {
fwrite(STDERR, 'Warning: APC <= 3.0.12 may cause fatal errors when running composer commands.'.PHP_EOL);
fwrite(STDERR, 'Update APC, or set apc.enable_cli or apc.cache_by_default to 0 in your php.ini.'.PHP_EOL);
}
}
if (!class_exists('Phar')) {
echo 'PHP\'s phar extension is *missing. Composer requires it to run. Enable the extension or recompile php without --disable-phar then try again.' . PHP_EOL;
exit(1);
}
Phar::mapPhar('composer.phar');
EOF;
// add warning once the pha*r is older than 60 days
if (preg_match('{^[a-f0-9]+$}', $this->version)) {
$warningTime = ((int) $this->versionDate->format('U')) + 60 * 86400;
$stub .= "define('COMPOSER_DEV_WARNING_TIME', $warningTime);\n";
}
return $stub . <<<'EOF'
require 'phar://composer.phar/bin/composer';
__HALT_COMPILER();
EOF;
}
private function getStub() { $stub = <<<'EOF' #!/usr/bin/env php <?php /* * This file is part of Composer. * * (c) Nils Adermann <naderman@naderman.de> * Jordi Boggiano <j.boggiano@seld.be> * * For the full copyright and license information, please view * the license that is located at the bottom of this file. */ // Avoid APC causing random fatal errors per https://github.com/composer/composer/issues/264 if (extension_loaded('apc') && filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN) && filter_var(ini_get('apc.cache_by_default'), FILTER_VALIDATE_BOOLEAN)) { if (version_compare(phpversion('apc'), '3.0.12', '>=')) { ini_set('apc.cache_by_default', 0); } else { fwrite(STDERR, 'Warning: APC <= 3.0.12 may cause fatal errors when running composer commands.'.PHP_EOL); fwrite(STDERR, 'Update APC, or set apc.enable_cli or apc.cache_by_default to 0 in your php.ini.'.PHP_EOL); } } if (!class_exists('Phar')) { echo 'PHP\'s phar extension is *missing. Composer requires it to run. Enable the extension or recompile php without --disable-phar then try again.' . PHP_EOL; exit(1); } Phar::mapPhar('composer.phar'); EOF; // add warning once the pha*r is older than 60 days if (preg_match('{^[a-f0-9]+$}', $this->version)) { $warningTime = ((int) $this->versionDate->format('U')) + 60 * 86400; $stub .= "define('COMPOSER_DEV_WARNING_TIME', $warningTime);\n"; } return $stub . <<<'EOF' require 'phar://composer.phar/bin/composer'; __HALT_COMPILER(); EOF; }
private function getStub()
    {
        $stub = <<<'EOF'
#!/usr/bin/env php
<?php
/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view
 * the license that is located at the bottom of this file.
 */
// Avoid APC causing random fatal errors per https://github.com/composer/composer/issues/264
if (extension_loaded('apc') && filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN) && filter_var(ini_get('apc.cache_by_default'), FILTER_VALIDATE_BOOLEAN)) {
    if (version_compare(phpversion('apc'), '3.0.12', '>=')) {
        ini_set('apc.cache_by_default', 0);
    } else {
        fwrite(STDERR, 'Warning: APC <= 3.0.12 may cause fatal errors when running composer commands.'.PHP_EOL);
        fwrite(STDERR, 'Update APC, or set apc.enable_cli or apc.cache_by_default to 0 in your php.ini.'.PHP_EOL);
    }
}
if (!class_exists('Phar')) {
    echo 'PHP\'s phar extension is *missing. Composer requires it to run. Enable the extension or recompile php without --disable-phar then try again.' . PHP_EOL;
    exit(1);
}
Phar::mapPhar('composer.phar');
EOF;

        // add warning once the pha*r is older than 60 days
        if (preg_match('{^[a-f0-9]+$}', $this->version)) {
            $warningTime = ((int) $this->versionDate->format('U')) + 60 * 86400;
            $stub .= "define('COMPOSER_DEV_WARNING_TIME', $warningTime);\n";
        }

        return $stub . <<<'EOF'
require 'phar://composer.phar/bin/composer';
__HALT_COMPILER();
EOF;
    }

Seeing this, I think it may be the problem here, because I directly used the createDefaultStub method to create the stub

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$phar->setStub($phar->createDefaultStub('install.php'));
$phar->setStub($phar->createDefaultStub('install.php'));
$phar->setStub($phar->createDefaultStub('install.php'));

Some modifications have been made with reference to the composer code:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$dirname = dirname(__DIR__);
$pharFile = $dirname . '/install.phar';
if (file_exists($pharFile)) {
unlink($pharFile);
}
function getStub()
{
$stub = <<<'EOF'
#!/usr/bin/env php
<?php
if (!class_exists('Phar')) {
echo 'PHP\'s phar extension is missing. Install requires it to run. Enable the extension or recompile php without --disable-phar then try again.' . PHP_EOL;
exit(1);
}
Phar::mapPhar('install.phar');
EOF;
return $stub . <<<'EOF'
require 'phar://install.phar/bin/install';
__HALT_COMPILER();
EOF;
}
$phar = new Phar($pharFile, 0, 'install.phar');
$phar->startBuffering();
$phar->buildFromDirectory($dirname);
$phar->delete('bin/build.php');
$phar->delete('.gitignore');
$phar->delete('install');
$content = file_get_contents($dirname . '/install');
$content = preg_replace('{^#!/usr/bin/env php\s*}', '', $content);
$phar->addFromString('bin/install', $content);
$phar->setStub(getStub());
$phar->stopBuffering();
$dirname = dirname(__DIR__); $pharFile = $dirname . '/install.phar'; if (file_exists($pharFile)) { unlink($pharFile); } function getStub() { $stub = <<<'EOF' #!/usr/bin/env php <?php if (!class_exists('Phar')) { echo 'PHP\'s phar extension is missing. Install requires it to run. Enable the extension or recompile php without --disable-phar then try again.' . PHP_EOL; exit(1); } Phar::mapPhar('install.phar'); EOF; return $stub . <<<'EOF' require 'phar://install.phar/bin/install'; __HALT_COMPILER(); EOF; } $phar = new Phar($pharFile, 0, 'install.phar'); $phar->startBuffering(); $phar->buildFromDirectory($dirname); $phar->delete('bin/build.php'); $phar->delete('.gitignore'); $phar->delete('install'); $content = file_get_contents($dirname . '/install'); $content = preg_replace('{^#!/usr/bin/env php\s*}', '', $content); $phar->addFromString('bin/install', $content); $phar->setStub(getStub()); $phar->stopBuffering();
$dirname = dirname(__DIR__);
$pharFile = $dirname . '/install.phar';

if (file_exists($pharFile)) {
    unlink($pharFile);
}

function getStub()
{
    $stub = <<<'EOF'
#!/usr/bin/env php
<?php
if (!class_exists('Phar')) {
    echo 'PHP\'s phar extension is missing. Install requires it to run. Enable the extension or recompile php without --disable-phar then try again.' . PHP_EOL;
    exit(1);
}
Phar::mapPhar('install.phar');
EOF;

    return $stub . <<<'EOF'
require 'phar://install.phar/bin/install';
__HALT_COMPILER();
EOF;
}

$phar = new Phar($pharFile, 0, 'install.phar');
$phar->startBuffering();
$phar->buildFromDirectory($dirname);
$phar->delete('bin/build.php');
$phar->delete('.gitignore');
$phar->delete('install');
$content = file_get_contents($dirname . '/install');
$content = preg_replace('{^#!/usr/bin/env php\s*}', '', $content);
$phar->addFromString('bin/install', $content);
$phar->setStub(getStub());
$phar->stopBuffering();

After trying again, you can omit php to run it.