1
0
Files
breadcrumb-the-shire/bin/module-cli-bootstrap.php

167 lines
4.2 KiB
PHP

<?php
declare(strict_types=1);
/**
* Shared CLI bootstrap and error policy for module runtime scripts.
*
* Policy:
* - first-party warnings/notices/deprecations => fail-fast (RuntimeException)
* - vendor warnings/notices/deprecations => ignored and counted
*/
function moduleCliProjectRoot(): string
{
return dirname(__DIR__);
}
function moduleCliVendorWarningsIgnoredTotal(): int
{
$count = $GLOBALS['module_cli_vendor_warnings_ignored'] ?? 0;
return is_int($count) ? $count : 0;
}
function moduleCliVendorWarningsIgnoredSince(int $before): int
{
return max(0, moduleCliVendorWarningsIgnoredTotal() - $before);
}
function moduleCliIncrementVendorWarningsIgnored(): void
{
$current = moduleCliVendorWarningsIgnoredTotal();
$GLOBALS['module_cli_vendor_warnings_ignored'] = $current + 1;
}
function moduleCliSeverityName(int $severity): string
{
return match ($severity) {
E_WARNING => 'E_WARNING',
E_NOTICE => 'E_NOTICE',
E_USER_WARNING => 'E_USER_WARNING',
E_USER_NOTICE => 'E_USER_NOTICE',
E_DEPRECATED => 'E_DEPRECATED',
E_USER_DEPRECATED => 'E_USER_DEPRECATED',
default => 'E_' . $severity,
};
}
function moduleCliInstallErrorPolicy(): void
{
static $installed = false;
if ($installed) {
return;
}
$installed = true;
error_reporting(E_ALL);
ini_set('display_errors', '0');
set_error_handler(static function (
int $severity,
string $message,
string $file = '',
int $line = 0
): bool {
$handledSeverities = [
E_WARNING,
E_NOTICE,
E_USER_WARNING,
E_USER_NOTICE,
E_DEPRECATED,
E_USER_DEPRECATED,
];
if (!in_array($severity, $handledSeverities, true)) {
return false;
}
if ((error_reporting() & $severity) === 0) {
return true;
}
$normalizedFile = str_replace('\\', '/', $file);
if (str_contains($normalizedFile, '/vendor/')) {
moduleCliIncrementVendorWarningsIgnored();
return true;
}
throw new RuntimeException(sprintf(
"First-party runtime warning (%s) in %s:%d: %s",
moduleCliSeverityName($severity),
$file,
$line,
$message
));
});
}
function moduleCliBootstrapApp(): void
{
static $booted = false;
if ($booted) {
return;
}
moduleCliInstallErrorPolicy();
$projectRoot = moduleCliProjectRoot();
chdir($projectRoot);
require_once $projectRoot . '/vendor/autoload.php';
require_once $projectRoot . '/config/config.php';
require_once $projectRoot . '/lib/Support/helpers.php';
$container = require $projectRoot . '/lib/App/registerContainer.php';
setAppContainer($container);
$booted = true;
}
/**
* Runs one module CLI step with shared bootstrap, failure handling and vendor warning summary.
*
* @param callable():int $runner
*/
function moduleCliRunStep(string $stepName, callable $runner): int
{
$warningsBefore = moduleCliVendorWarningsIgnoredTotal();
moduleCliBootstrapApp();
$exitCode = 0;
try {
$exitCode = $runner();
} catch (Throwable $exception) {
fwrite(STDERR, sprintf("%s: FAILED: %s\n", $stepName, $exception->getMessage()));
$exitCode = 1;
}
$vendorWarningsIgnored = moduleCliVendorWarningsIgnoredSince($warningsBefore);
fwrite(STDOUT, sprintf("%s: vendor_warnings_ignored=%d\n", $stepName, $vendorWarningsIgnored));
return $exitCode;
}
/**
* Executes a callable under a non-blocking file lock.
*
* @param callable():int $runner
*/
function moduleCliWithFileLock(string $lockFile, string $busyMessage, callable $runner): int
{
$lockDir = dirname($lockFile);
if (!is_dir($lockDir) && !mkdir($lockDir, 0775, true) && !is_dir($lockDir)) {
throw new RuntimeException("Failed to create lock directory: {$lockDir}");
}
$lock = fopen($lockFile, 'c');
if ($lock === false || !flock($lock, LOCK_EX | LOCK_NB)) {
fwrite(STDERR, $busyMessage . PHP_EOL);
return 0;
}
try {
return $runner();
} finally {
flock($lock, LOCK_UN);
fclose($lock);
}
}