- What is the canonical way to write a CLI entry file?
- Detecting that you are inside a PHAR
- Building a PHAR with Box as a dependency
A conventional CLI entry file looks like this (see bellow for further explanations):
#!/usr/bin/env php
<?php
declare(strict_types=1);
namespace Acme;
use function in_array;
use const PHP_EOL;
use const PHP_SAPI;
use RuntimeException;
if (false === in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
echo PHP_EOL.'This app may only be invoked from a command line, got "'.PHP_SAPI.'"'.PHP_EOL;
exit(1);
}
(static function (): void {
if (file_exists($autoload = __DIR__.'/../../../autoload.php')) {
// Is installed via Composer
include_once $autoload;
return;
}
if (file_exists($autoload = __DIR__.'/../vendor/autoload.php')) {
// Is installed locally
include_once $autoload;
return;
}
throw new RuntimeException('Unable to find the Composer autoloader.');
})();
// Execute the application
The shebang #!/usr/bin/env php
is required to the auto-detection of the type of the script. This allows to use it as
follows:
chmod +x bin/acme.php
./bin/acme.php
php bin/acme.php # still works
# Without the shebang line, you can only use the latter
In other words it is not necessary, but a nice to have if you want to make your file executable.
For PHP, available SAPIs are: Apache2 (mod_php), FPM, CGI, FastCGI and CLI. There is a few other variants but those are the most commons ones. For more information, see the official PHP doc.
So the following:
if (false === in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
echo PHP_EOL.'This app may only be invoked from a command line, got "'.PHP_SAPI.'"'.PHP_EOL;
exit(1);
}
is purely ot make sure your CLI application is not executed in a non CLI context (for example via a web server). Doing so prevents you to have to worry about web-server related vulnerabilities such as HTTPoxy.
cli
is the standard default you will getphpdbg
when executing PHP with PHPDBGembed
if you compile the PHP/ZE into another program
When developing a CLI application, you generally only need to worry about your local autoloader:
include_once __DIR__.'/../vendor/autoload.php';
However, if the application is also published as a Composer package, then the autoloader may be found in a different location:
include_once __DIR__.'/../../../autoload.php';
In either cases however, it could be the autoloader file is missing (e.g. if the dependencies are not installed yet).
So it is wise to wrap them in a file_exist()
check and provide a user-friendly error when no autoloader could be
found.
The easiest way to know if your script is executed from within a PHAR is to run the following:
$isInPhar = '' !== Phar::running(false);
See Phar::running() for more information.
If you need to include Box as part of your dependencies and include it within your PHAR, you will probably encounter the following issue when building your PHAR:
Could not dump the autoloader.
[...]
Could not scan for classes inside "/path/to/vendor/humbug/php-scoper/vendor-hotfix/" which does not appear to be a file nor a folder
This is because by default, Box does not include VCS or dot files which results in the directory vendor/humbug/php-scoper/vendor-hotfix/
to be excluded (as it becomes an empty directory). To circumvent that, you will likely need:
{
"directories": ["vendor/humbug/php-scoper/vendor-hotfix"]
}
Note that as a result you may want to use the force-autodiscovery
setting.