getParams(); } elseif ($app instanceof Application) { $class = get_class($app); $params = [ "class" => $class, "projdir" => $app::PROJDIR, "vendor" => $app::VENDOR, "projcode" => $app::PROJCODE, "datadir" => $app::DATADIR, "etcdir" => $app::ETCDIR, "vardir" => $app::VARDIR, "cachedir" => $app::CACHEDIR, "logdir" => $app::LOGDIR, "appgroup" => $app::APPGROUP, "name" => $app::NAME, "title" => $app::TITLE, ]; } elseif (self::isa_Application($app)) { $class = $app; $params = [ "class" => $class, "projdir" => constant("$app::PROJDIR"), "vendor" => constant("$app::VENDOR"), "projcode" => constant("$app::PROJCODE"), "datadir" => constant("$app::DATADIR"), "etcdir" => constant("$app::ETCDIR"), "vardir" => constant("$app::VARDIR"), "cachedir" => constant("$app::CACHEDIR"), "logdir" => constant("$app::LOGDIR"), "appgroup" => constant("$app::APPGROUP"), "name" => constant("$app::NAME"), "title" => constant("$app::TITLE"), ]; } elseif (is_array($app)) { $params = $app; } else { throw exceptions::invalid_type($app, "app", Application::class); } return $params; } protected static ?self $app = null; /** * @param Application|string|array $app * @param Application|string|array|null $proj */ static function with($app, $proj=null): self { $params = self::get_params($app); $proj ??= self::params_getenv(); $proj ??= self::$app; $proj_params = $proj !== null? self::get_params($proj): null; if ($proj_params !== null) { A::merge($params, cl::select($proj_params, [ "projdir", "vendor", "projcode", "cwd", "datadir", "etcdir", "vardir", "cachedir", "logdir", "profile", "facts", "debug", ])); } return new static($params, $proj_params !== null); } static function init($app, $proj=null): void { self::$app = static::with($app, $proj); } static function get(): self { return self::$app ??= new static(null); } static function params_putenv(): void { $params = serialize(self::get()->getParams()); putenv("NULIB_APP_app_params=$params"); } static function params_getenv(): ?array { $params = getenv("NULIB_APP_app_params"); if ($params === false) return null; return unserialize($params); } static function get_profile(?bool &$productionMode=null): string { return self::get()->getProfile($productionMode); } static function is_production_mode(): bool { return self::get()->isProductionMode(); } static function is_prod(): bool { return self::get_profile() === ref_profiles::PROD; } static function is_test(): bool { return self::get_profile() === ref_profiles::TEST; } static function is_devel(): bool { return self::get_profile() === ref_profiles::DEVEL; } static function set_profile(?string $profile=null, ?bool $productionMode=null): void { self::get()->setProfile($profile, $productionMode); } const FACT_WEB_APP = "web-app"; const FACT_CLI_APP = "cli-app"; static final function is_fact(string $fact, $value=true): bool { return self::get()->isFact($fact, $value); } static final function set_fact(string $fact, $value=true): void { self::get()->setFact($fact, $value); } static function is_debug(): bool { return self::get()->isDebug(); } static function set_debug(?bool $debug=true): void { self::get()->setDebug($debug); } /** * @var array répertoires vendor exprimés relativement à PROJDIR */ const DEFAULT_VENDOR = [ "bindir" => "vendor/bin", "autoload" => "vendor/autoload.php", ]; function __construct(?array $params, bool $useProjParams=false) { if ($useProjParams) { [ "projdir" => $projdir, "vendor" => $vendor, "projcode" => $projcode, "datadir" => $datadir, "etcdir" => $etcdir, "vardir" => $vardir, "cachedir" => $cachedir, "logdir" => $logdir, ] = $params; $cwd = $params["cwd"] ?? null; $datadirIsDefined = true; } else { # projdir $projdir = $params["projdir"] ?? null; if ($projdir === null) { global $_composer_autoload_path, $_composer_bin_dir; $autoload = $_composer_autoload_path ?? null; $bindir = $_composer_bin_dir ?? null; if ($autoload !== null) { $vendor = preg_replace('/\/[^\/]+\.php$/', "", $autoload); $bindir ??= "$vendor/bin"; $projdir = preg_replace('/\/[^\/]+$/', "", $vendor); $params["vendor"] = [ "autoload" => $autoload, "bindir" => $bindir, ]; } } if ($projdir === null) $projdir = "."; $projdir = path::abspath($projdir); # vendor $vendor = $params["vendor"] ?? self::DEFAULT_VENDOR; $vendor["bindir"] = path::reljoin($projdir, $vendor["bindir"]); $vendor["autoload"] = path::reljoin($projdir, $vendor["autoload"]); # projcode $projcode = $params["projcode"] ?? null; if ($projcode === null) { $projcode = str::without_suffix("-app", path::basename($projdir)); } $PROJCODE = str_replace("-", "_", strtoupper($projcode)); # cwd $cwd = $params["cwd"] ?? null; # datadir $datadir = getenv("${PROJCODE}_DATADIR"); $datadirIsDefined = $datadir !== false; if ($datadir === false) $datadir = $params["datadir"] ?? null; if ($datadir === null) $datadir = "devel"; $datadir = path::reljoin($projdir, $datadir); # etcdir $etcdir = getenv("${PROJCODE}_ETCDIR"); if ($etcdir === false) $etcdir = $params["etcdir"] ?? null; if ($etcdir === null) $etcdir = "etc"; $etcdir = path::reljoin($datadir, $etcdir); # vardir $vardir = getenv("${PROJCODE}_VARDIR"); if ($vardir === false) $vardir = $params["vardir"] ?? null; if ($vardir === null) $vardir = "var"; $vardir = path::reljoin($datadir, $vardir); # cachedir $cachedir = getenv("${PROJCODE}_CACHEDIR"); if ($cachedir === false) $cachedir = $params["cachedir"] ?? null; if ($cachedir === null) $cachedir = "cache"; $cachedir = path::reljoin($vardir, $cachedir); # logdir $logdir = getenv("${PROJCODE}_LOGDIR"); if ($logdir === false) $logdir = $params["logdir"] ?? null; if ($logdir === null) $logdir = "log"; $logdir = path::reljoin($datadir, $logdir); } # cwd $cwd ??= getcwd(); # profile $this->profileManager = new ProfileManager([ "app" => true, "name" => $projcode, "default_profile" => $datadirIsDefined? "prod": "devel", "profile" => $params["profile"] ?? null, ]); # $facts $this->facts = $params["facts"] ?? null; # debug $this->debug = $params["debug"] ?? null; $this->projdir = $projdir; $this->vendor = $vendor; $this->projcode = $projcode; $this->cwd = $cwd; $this->datadir = $datadir; $this->etcdir = $etcdir; $this->vardir = $vardir; $this->cachedir = $cachedir; $this->logdir = $logdir; # name, title $appgroup = $params["appgroup"] ?? null; $name = $params["name"] ?? $params["class"] ?? null; if ($name === null) { $name = $projcode; } else { # si $name est une classe, enlever le package et normaliser i.e # my\package\MyApplication --> my-application.php $name = preg_replace('/.*\\\\/', "", $name); $name = str::camel2us($name, false, "-"); $name = str::without_suffix("-app", $name); } $this->appgroup = $appgroup; $this->name = $name; $this->title = $params["title"] ?? null; } ############################################################################# # Paramètres partagés par tous les scripts d'un projet (et les scripts lancés # à partir d'une application de ce projet) protected string $projdir; function getProjdir(): string { return $this->projdir; } protected array $vendor; function getVendorBindir(): string { return $this->vendor["bindir"]; } function getVendorAutoload(): string { return $this->vendor["autoload"]; } protected string $projcode; function getProjcode(): string { return $this->projcode; } protected string $cwd; function getCwd(): string { return $this->cwd; } protected string $datadir; function getDatadir(): string { return $this->datadir; } protected string $etcdir; function getEtcdir(): string { return $this->etcdir; } protected string $vardir; function getVardir(): string { return $this->vardir; } protected string $cachedir; function getCachedir(): string { return $this->cachedir; } protected string $logdir; function getLogdir(): string { return $this->logdir; } protected ProfileManager $profileManager; function getProfile(?bool &$productionMode=null): string { return $this->profileManager->getProfile($productionMode); } function isProductionMode(): bool { return $this->profileManager->isProductionMode(); } function setProfile(?string $profile, ?bool $productionMode=null): void { $this->profileManager->setProfile($profile, $productionMode); } protected ?array $facts; function isFact(string $fact, $value=true): bool { return ($this->facts[$fact] ?? false) === $value; } function setFact(string $fact, $value=true): void { $this->facts[$fact] = $value; } protected ?bool $debug; function isDebug(): bool { $debug = $this->debug; if ($debug === null) { $debug = defined("DEBUG")? DEBUG: null; $DEBUG = getenv("DEBUG"); $debug ??= $DEBUG !== false? $DEBUG: null; $debug ??= config::k("debug"); $debug ??= false; $this->debug = $debug; } return $debug; } function setDebug(bool $debug=true): void { $this->debug = $debug; } /** * @param ?string|false $profile * * false === pas de profil * null === profil par défaut */ function withProfile(string $file, $profile): string { if ($profile !== false) { $profile ??= $this->getProfile(); [$dir, $filename] = path::split($file); $basename = path::basename($filename); $ext = path::ext($file); $file = path::join($dir, "$basename.$profile$ext"); } return $file; } function findFile(array $dirs, array $names, $profile=null): string { # d'abord chercher avec le profil if ($profile !== false) { foreach ($dirs as $dir) { foreach ($names as $name) { $file = path::join($dir, $name); $file = $this->withProfile($file, $profile); if (file_exists($file)) return $file; } } } # puis sans profil foreach ($dirs as $dir) { foreach ($names as $name) { $file = path::join($dir, $name); if (file_exists($file)) return $file; } } # la valeur par défaut est avec profil return $this->withProfile(path::join($dirs[0], $names[0]), $profile); } function fencedJoin(string $basedir, ?string ...$paths): string { $path = path::reljoin($basedir, ...$paths); if (!path::is_within($path, $basedir)) { throw exceptions::invalid_value($path, "path"); } return $path; } ############################################################################# # Paramètres spécifiques à cette application protected ?string $appgroup; function getAppgroup(): ?string { return $this->appgroup; } protected string $name; function getName(): ?string { return $this->name; } protected ?string $title; function getTitle(): ?string { return $this->title; } ############################################################################# # Méthodes outils /** recréer le tableau des paramètres */ function getParams(): array { return [ "projdir" => $this->projdir, "vendor" => $this->vendor, "projcode" => $this->projcode, "cwd" => $this->cwd, "datadir" => $this->datadir, "etcdir" => $this->etcdir, "vardir" => $this->vardir, "cachedir" => $this->cachedir, "logdir" => $this->logdir, "profile" => $this->getProfile(), "facts" => $this->facts, "debug" => $this->debug, "appgroup" => $this->appgroup, "name" => $this->name, "title" => $this->title, ]; } /** * obtenir le chemin vers le fichier de configuration. par défaut, retourner * une valeur de la forme "$ETCDIR/$name[.$profile].conf" */ function getEtcfile(?string $name=null, $profile=null): string { $name ??= "{$this->name}.conf"; return $this->findFile([$this->etcdir], [$name], $profile); } /** * obtenir le chemin vers le fichier de travail. par défaut, retourner une * valeur de la forme "$VARDIR/$appgroup/$name[.$profile].tmp" */ function getVarfile(?string $name=null, $profile=null): string { $name ??= "{$this->name}.tmp"; $file = $this->fencedJoin($this->vardir, $this->appgroup, $name); $file = $this->withProfile($file, $profile); sh::mkdirof($file); return $file; } /** * obtenir le chemin vers le fichier de cache. par défaut, retourner une * valeur de la forme "$CACHEDIR/$appgroup/$name[.$profile].cache" */ function getCachefile(?string $name=null, $profile=null): string { $name ??= "{$this->name}.cache"; $file = $this->fencedJoin($this->cachedir, $this->appgroup, $name); $file = $this->withProfile($file, $profile); sh::mkdirof($file); return $file; } /** * obtenir le chemin vers le fichier de log. par défaut, retourner une * valeur de la forme "$LOGDIR/$appgroup/$name.log" (sans le profil, parce * qu'il s'agit du fichier de log par défaut) * * Si $name est spécifié, la valeur retournée sera de la forme * "$LOGDIR/$appgroup/$basename[.$profile].$ext" */ function getLogfile(?string $name=null, $profile=null): string { if ($name === null) { $name = "{$this->name}.log"; $profile ??= false; } $logfile = $this->fencedJoin($this->logdir, $this->appgroup, $name); $logfile = $this->withProfile($logfile, $profile); sh::mkdirof($logfile); return $logfile; } /** * obtenir le chemin absolu vers un fichier de travail * - si le chemin est absolu, il est inchangé * - sinon le chemin est exprimé par rapport à $vardir/$appgroup * * is $ensureDir, créer le répertoire du fichier s'il n'existe pas déjà * * la différence avec {@link self::getVarfile()} est que le fichier peut * au final être situé ailleurs que dans $vardir. de plus, il n'y a pas de * valeur par défaut pour $file */ function getWorkfile(string $file, $profile=null, bool $ensureDir=true): string { $file = path::reljoin($this->vardir, $this->appgroup, $file); $file = $this->withProfile($file, $profile); if ($ensureDir) sh::mkdirof($file); return $file; } /** * obtenir le chemin absolu vers un fichier spécifié par l'utilisateur. * - si le chemin commence par /, il est laissé en l'état * - si le chemin commence par ./ ou ../, il est exprimé par rapport à $cwd * - sinon le chemin est exprimé par rapport à $vardir/$appgroup * * la différence est avec {@link self::getVarfile()} est que le fichier peut * au final être situé ailleurs que dans $vardir. de plus, il n'y a pas de * valeur par défaut pour $file */ function getUserfile(string $file): string { if (path::is_qualified($file)) { return path::reljoin($this->cwd, $file); } else { return path::reljoin($this->vardir, $this->appgroup, $file); } } protected ?RunFile $runfile = null; function getRunfile(): RunFile { $name = $this->name; $runfile = $this->getWorkfile($name); $logfile = $this->getLogfile("$name.out", false); return $this->runfile ??= new RunFile($name, $runfile, $logfile); } protected ?array $lockFiles = null; function getLockfile(?string $name=null): LockFile { $this->lockFiles[$name] ??= $this->getRunfile()->getLockFile($name, $this->title); return $this->lockFiles[$name]; } ############################################################################# const EC_FORK_CHILD = 250; const EC_FORK_PARENT = 251; const EC_DISABLED = 252; const EC_LOCKED = 253; const EC_BAD_COMMAND = 254; const EC_UNEXPECTED = 255; ############################################################################# static bool $dispach_signals = false; static function install_signal_handler(bool $allow=true): void { if (!$allow) return; $signalHandler = function(int $signo, $siginfo) { throw new ExitError(128 + $signo); }; pcntl_signal(SIGHUP, $signalHandler); pcntl_signal(SIGINT, $signalHandler); pcntl_signal(SIGQUIT, $signalHandler); pcntl_signal(SIGTERM, $signalHandler); self::$dispach_signals = true; } static function _dispatch_signals() { if (self::$dispach_signals) pcntl_signal_dispatch(); } ############################################################################# static ?func $bgapplication_enabled = null; /** * spécifier la fonction permettant de vérifier si l'exécution de tâches * de fond est autorisée. Si cette méthode n'est pas utilisée, par défaut, * les tâches planifiées sont autorisées * * si $func===true, spécifier une fonction qui retourne toujours vrai * si $func===false, spécifiée une fonction qui retourne toujours faux * sinon, $func doit être une fonction valide */ static function set_bgapplication_enabled($func): void { if (is_bool($func)) { $enabled = $func; $func = function () use ($enabled) { return $enabled; }; } self::$bgapplication_enabled = func::with($func); } /** * Si les exécutions en tâche de fond sont autorisée, retourner. Sinon * afficher une erreur et quitter l'application */ static function check_bgapplication_enabled(bool $forceEnabled=false): void { if (self::$bgapplication_enabled === null || $forceEnabled) return; if (!self::$bgapplication_enabled->invoke()) { throw new ExitError(self::EC_DISABLED, "Planifications désactivées. La tâche n'a pas été lancée"); } } ############################################################################# static function action(?string $title, ?int $maxSteps=null): void { self::get()->getRunfile()->action($title, $maxSteps); } static function step(int $nbSteps=1): void { self::get()->getRunfile()->step($nbSteps); } }