maj nur/sery
This commit is contained in:
parent
914de961eb
commit
9a48a2bee4
|
@ -213,4 +213,23 @@ class A {
|
|||
static final function filter_not_equals($dest, $value): void { self::filter_if($dest, cv::not_equals($value)); }
|
||||
static final function filter_same($dest, $value): void { self::filter_if($dest, cv::same($value)); }
|
||||
static final function filter_not_same($dest, $value): void { self::filter_if($dest, cv::not_same($value)); }
|
||||
|
||||
#############################################################################
|
||||
|
||||
static final function sort(?array &$array, int $flags=SORT_REGULAR, bool $assoc=false): void {
|
||||
if ($array === null) return;
|
||||
if ($assoc) asort($array, $flags);
|
||||
else sort($array, $flags);
|
||||
}
|
||||
|
||||
static final function ksort(?array &$array, int $flags=SORT_REGULAR): void {
|
||||
if ($array === null) return;
|
||||
ksort($array, $flags);
|
||||
}
|
||||
|
||||
static final function usort(?array &$array, array $keys, bool $assoc=false): void {
|
||||
if ($array === null) return;
|
||||
if ($assoc) uasort($array, cl::compare($keys));
|
||||
else usort($array, cl::compare($keys));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ class ExitError extends Error {
|
|||
/** @var ?string */
|
||||
protected $userMessage;
|
||||
|
||||
function haveMessage(): bool {
|
||||
function haveUserMessage(): bool {
|
||||
return $this->userMessage !== null;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
<?php
|
||||
namespace nulib\app;
|
||||
|
||||
use nulib\A;
|
||||
use nulib\app;
|
||||
use nulib\cl;
|
||||
use nulib\file\SharedFile;
|
||||
use nulib\os\path;
|
||||
use nulib\os\sh;
|
||||
use nulib\output\msg;
|
||||
use nulib\php\time\DateTime;
|
||||
use nulib\php\time\Elapsed;
|
||||
use nulib\str;
|
||||
|
||||
/**
|
||||
|
@ -18,21 +22,21 @@ class RunFile {
|
|||
|
||||
const NAME = null;
|
||||
|
||||
function __construct(?string $name, string $file, ?string $logfile=null) {
|
||||
function __construct(?string $name, string $file, ?string $outfile=null) {
|
||||
$file = path::ensure_ext($file, self::RUN_EXT);
|
||||
$this->name = $name ?? static::NAME;
|
||||
$this->file = new SharedFile($file);
|
||||
$this->logfile = $logfile;
|
||||
$this->outfile = $outfile;
|
||||
}
|
||||
|
||||
protected ?string $name;
|
||||
|
||||
protected SharedFile $file;
|
||||
|
||||
protected ?string $logfile;
|
||||
protected ?string $outfile;
|
||||
|
||||
function getLogfile(): ?string {
|
||||
return $this->logfile;
|
||||
function getOutfile(): ?string {
|
||||
return $this->outfile;
|
||||
}
|
||||
|
||||
protected static function merge(array $data, array $merge): array {
|
||||
|
@ -41,29 +45,23 @@ class RunFile {
|
|||
], $merge);
|
||||
}
|
||||
|
||||
protected function initData(bool $forStart=true): array {
|
||||
if ($forStart) {
|
||||
$pid = posix_getpid();
|
||||
$dateStart = new DateTime();
|
||||
} else {
|
||||
$pid = $dateStart = null;
|
||||
}
|
||||
protected function initData(): array {
|
||||
return [
|
||||
"name" => $this->name,
|
||||
"id" => bin2hex(random_bytes(16)),
|
||||
"pg_pid" => null,
|
||||
"pid" => $pid,
|
||||
"pgid" => null,
|
||||
"pid" => null,
|
||||
"serial" => 0,
|
||||
# lock
|
||||
"locked" => false,
|
||||
"date_lock" => null,
|
||||
"date_release" => null,
|
||||
# run
|
||||
"logfile" => $this->logfile,
|
||||
"date_start" => $dateStart,
|
||||
"logfile" => $this->outfile,
|
||||
"date_start" => null,
|
||||
"date_stop" => null,
|
||||
"exitcode" => null,
|
||||
"is_done" => null,
|
||||
"is_reaped" => null,
|
||||
"is_ack_done" => null,
|
||||
# action
|
||||
"action" => null,
|
||||
"action_date_start" => null,
|
||||
|
@ -73,9 +71,19 @@ class RunFile {
|
|||
];
|
||||
}
|
||||
|
||||
function reset(bool $delete=false) {
|
||||
$file = $this->file;
|
||||
if ($delete) {
|
||||
$file->close();
|
||||
unlink($file->getFile());
|
||||
} else {
|
||||
$file->ftruncate();
|
||||
}
|
||||
}
|
||||
|
||||
function read(): array {
|
||||
$data = $this->file->unserialize();
|
||||
if (!is_array($data)) $data = $this->initData(false);
|
||||
if (!is_array($data)) $data = $this->initData();
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
@ -84,7 +92,7 @@ class RunFile {
|
|||
$file->lockWrite();
|
||||
$data = $file->unserialize(null, false, true);
|
||||
if (!is_array($data)) {
|
||||
$data = $this->initData(false);
|
||||
$data = $this->initData();
|
||||
$file->ftruncate();
|
||||
$file->serialize($data, false, true);
|
||||
}
|
||||
|
@ -160,14 +168,38 @@ class RunFile {
|
|||
# cycle de vie de l'application
|
||||
|
||||
/**
|
||||
* indiquer que l'application démarre. l'état est entièrement réinitialisé,
|
||||
* sauf le PID du leader qui est laissé en l'état
|
||||
* Préparer le démarrage de l'application. Cette méhode est appelée par un
|
||||
* script externe qui doit préparer le démarrage du script
|
||||
*
|
||||
* - démarrer un groupe de process dont le process courant est le leader
|
||||
*/
|
||||
function wfPrepare(?int &$pgid=null): void {
|
||||
$this->update(function (array $data) use (&$pgid) {
|
||||
posix_setsid();
|
||||
$pgid = posix_getpid();
|
||||
return cl::merge($this->initData(), [
|
||||
"pgid" => $pgid,
|
||||
"pid" => null,
|
||||
"date_start" => new DateTime(),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/** indiquer que l'application démarre. */
|
||||
function wfStart(): void {
|
||||
$this->update(function (array $data) {
|
||||
return cl::merge($this->initData(), [
|
||||
"pg_pid" => $data["pg_pid"],
|
||||
]);
|
||||
$pid = posix_getpid();
|
||||
if ($data["pgid"] !== null) {
|
||||
A::merge($data, [
|
||||
"pid" => $pid,
|
||||
]);
|
||||
} else {
|
||||
$data = cl::merge($this->initData(), [
|
||||
"pid" => $pid,
|
||||
"date_start" => new DateTime(),
|
||||
]);
|
||||
}
|
||||
return $data;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -183,13 +215,12 @@ class RunFile {
|
|||
return $data["date_start"] !== null && $data["date_stop"] === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* vérifier si l'application marquée comme démarrée tourne réellement
|
||||
*/
|
||||
function isRunning(?array $data=null): bool {
|
||||
$data ??= $this->read();
|
||||
if ($data["date_start"] === null) return false;
|
||||
if ($data["date_stop"] !== null) return false;
|
||||
function _getCid(array $data=null): int {
|
||||
if ($data["pgid"] !== null) return -$data["pgid"];
|
||||
else return $data["pid"];
|
||||
}
|
||||
|
||||
function _isRunning(array $data=null): bool {
|
||||
if (!posix_kill($data["pid"], 0)) {
|
||||
switch (posix_get_last_error()) {
|
||||
case 1: #PCNTL_EPERM:
|
||||
|
@ -208,10 +239,22 @@ class RunFile {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* vérifier si l'application marquée comme démarrée tourne réellement
|
||||
*/
|
||||
function isRunning(?array $data=null): bool {
|
||||
$data ??= $this->read();
|
||||
if ($data["date_start"] === null) return false;
|
||||
if ($data["date_stop"] !== null) return false;
|
||||
return $this->_isRunning($data);
|
||||
}
|
||||
|
||||
/** indiquer que l'application s'arrête */
|
||||
function wfStop(): void {
|
||||
$this->update(function (array $data) {
|
||||
return ["date_stop" => new DateTime()];
|
||||
return [
|
||||
"date_stop" => new DateTime(),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -228,88 +271,75 @@ class RunFile {
|
|||
}
|
||||
|
||||
/** après l'arrêt de l'application, mettre à jour le code de retour */
|
||||
function wfStopped(int $exitcode): void {
|
||||
function wfReaped(int $exitcode): void {
|
||||
$this->update(function (array $data) use ($exitcode) {
|
||||
return [
|
||||
"pg_pid" => null,
|
||||
"pgid" => null,
|
||||
"date_stop" => $data["date_stop"] ?? new DateTime(),
|
||||
"exitcode" => $exitcode,
|
||||
"is_reaped" => true,
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* comme {@link self::isStopped()} mais ne renvoie true qu'une seule fois si
|
||||
* $updateDone==true
|
||||
*/
|
||||
function isDone(?array &$data=null, bool $updateDone=true): bool {
|
||||
$done = false;
|
||||
$this->update(function (array $ldata) use (&$done, &$data, $updateDone) {
|
||||
$data = $ldata;
|
||||
if ($data["date_start"] === null || $data["date_stop"] === null || $data["is_done"]) {
|
||||
return false;
|
||||
private static function kill(int $pid, int $signal, ?string &$reason=null): bool {
|
||||
if (!posix_kill($pid, $signal)) {
|
||||
switch (posix_get_last_error()) {
|
||||
case PCNTL_ESRCH:
|
||||
$reason = "process inexistant";
|
||||
break;
|
||||
case PCNTL_EPERM:
|
||||
$reason = "process non accessible";
|
||||
break;
|
||||
case PCNTL_EINVAL:
|
||||
$reason = "signal invalide";
|
||||
break;
|
||||
}
|
||||
$done = true;
|
||||
if ($updateDone) return ["is_done" => $done];
|
||||
else return null;
|
||||
});
|
||||
return $done;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
# gestion des actions
|
||||
|
||||
/** indiquer le début d'une action */
|
||||
function action(?string $title, ?int $maxSteps=null): void {
|
||||
$this->update(function (array $data) use ($title, $maxSteps) {
|
||||
return [
|
||||
"action" => $title,
|
||||
"action_date_start" => new DateTime(),
|
||||
"action_max_step" => $maxSteps,
|
||||
"action_current_step" => 0,
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/** indiquer qu'une étape est franchie dans l'action en cours */
|
||||
function step(int $nbSteps=1): void {
|
||||
$this->update(function (array $data) use ($nbSteps) {
|
||||
return [
|
||||
"action_date_step" => new DateTime(),
|
||||
"action_current_step" => $data["action_current_step"] + $nbSteps,
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
# Divers
|
||||
|
||||
function getLockFile(?string $name=null, ?string $title=null): LockFile {
|
||||
$ext = self::LOCK_EXT;
|
||||
if ($name !== null) $ext = ".$name$ext";
|
||||
$file = path::ensure_ext($this->file->getFile(), $ext, self::RUN_EXT);
|
||||
$name = str::join("/", [$this->name, $name]);
|
||||
return new LockFile($file, $name, $title);
|
||||
}
|
||||
|
||||
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
# Gestionnaire de tâches (tm_*)
|
||||
|
||||
/** démarrer un groupe de process dont le process courant est le leader */
|
||||
function tm_startPg(): void {
|
||||
$this->update(function (array $data) {
|
||||
posix_setsid();
|
||||
return [
|
||||
"pg_pid" => posix_getpid(),
|
||||
];
|
||||
});
|
||||
function wfKill(?string &$reason=null): bool {
|
||||
$data = $this->read();
|
||||
$pid = $this->_getCid($data);
|
||||
$stopped = false;
|
||||
$timeout = 10;
|
||||
$delay = 300000;
|
||||
while (--$timeout >= 0) {
|
||||
if (!self::kill($pid, SIGTERM, $reason)) return false;
|
||||
usleep($delay);
|
||||
$delay = 1000000; // attendre 1 seconde à partir de la deuxième fois
|
||||
if (!$this->_isRunning($data)) {
|
||||
$stopped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$stopped) {
|
||||
$timeout = 3;
|
||||
$delay = 300000;
|
||||
while (--$timeout >= 0) {
|
||||
if (!self::kill($pid, SIGKILL, $reason)) return false;
|
||||
usleep($delay);
|
||||
$delay = 1000000; // attendre 1 seconde à partir de la deuxième fois
|
||||
if (!$this->_isRunning($data)) {
|
||||
$stopped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($stopped) {
|
||||
sh::_waitpid($pid, $exitcode);
|
||||
$this->wfReaped($exitcode);
|
||||
}
|
||||
return $stopped;
|
||||
}
|
||||
|
||||
/**
|
||||
* vérifier si on est dans le cas où la tâche devrait tourner mais en réalité
|
||||
* ce n'est pas le cas
|
||||
*/
|
||||
function tm_isUndead(?int $pid=null): bool {
|
||||
function _isUndead(?int $pid=null): bool {
|
||||
$data = $this->read();
|
||||
if ($data["date_start"] === null) return false;
|
||||
if ($data["date_stop"] !== null) return false;
|
||||
|
@ -332,23 +362,135 @@ class RunFile {
|
|||
return false;
|
||||
}
|
||||
|
||||
function tm_isReapable(): bool {
|
||||
$data = $this->read();
|
||||
return $data["date_stop"] !== null && $data["exitcode"] === null;
|
||||
/**
|
||||
* comme {@link self::isStopped()} mais ne renvoie true qu'une seule fois si
|
||||
* $updateDone==true
|
||||
*/
|
||||
function isDone(?array &$data=null, bool $updateDone=true): bool {
|
||||
$done = false;
|
||||
$this->update(function (array $ldata) use (&$done, &$data, $updateDone) {
|
||||
$data = $ldata;
|
||||
if ($data["date_start"] === null || $data["date_stop"] === null || $data["is_ack_done"]) {
|
||||
return false;
|
||||
}
|
||||
$done = true;
|
||||
if ($updateDone) return ["is_ack_done" => $done];
|
||||
else return null;
|
||||
});
|
||||
return $done;
|
||||
}
|
||||
|
||||
/** marquer la tâche comme terminée */
|
||||
function tm_reap(?int $pid=null): void {
|
||||
$data = $this->read();
|
||||
$pid ??= $data["pid"];
|
||||
pcntl_waitpid($pid, $status);
|
||||
$exitcode = pcntl_wifexited($status)? pcntl_wexitstatus($status): 127;
|
||||
$this->update(function (array $data) use ($exitcode) {
|
||||
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
# gestion des actions
|
||||
|
||||
/** indiquer le début d'une action */
|
||||
function action(?string $title, ?int $maxSteps=null): void {
|
||||
$this->update(function (array $data) use ($title, $maxSteps) {
|
||||
return [
|
||||
"pg_pid" => null,
|
||||
"date_stop" => $data["date_stop"] ?? new DateTime(),
|
||||
"exitcode" => $data["exitcode"] ?? $exitcode,
|
||||
"action" => $title,
|
||||
"action_date_start" => new DateTime(),
|
||||
"action_max_step" => $maxSteps,
|
||||
"action_current_step" => 0,
|
||||
];
|
||||
});
|
||||
app::_dispatch_signals();
|
||||
}
|
||||
|
||||
/** indiquer qu'une étape est franchie dans l'action en cours */
|
||||
function step(int $nbSteps=1): void {
|
||||
$this->update(function (array $data) use ($nbSteps) {
|
||||
return [
|
||||
"action_date_step" => new DateTime(),
|
||||
"action_current_step" => $data["action_current_step"] + $nbSteps,
|
||||
];
|
||||
});
|
||||
app::_dispatch_signals();
|
||||
}
|
||||
|
||||
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
# Divers
|
||||
|
||||
function getLockFile(?string $name=null, ?string $title=null): LockFile {
|
||||
$ext = self::LOCK_EXT;
|
||||
if ($name !== null) $ext = ".$name$ext";
|
||||
$file = path::ensure_ext($this->file->getFile(), $ext, self::RUN_EXT);
|
||||
$name = str::join("/", [$this->name, $name]);
|
||||
return new LockFile($file, $name, $title);
|
||||
}
|
||||
|
||||
function getDesc(?array $data=null, bool $withAction=true): ?array {
|
||||
$data ??= $this->read();
|
||||
$desc = $data["name"];
|
||||
$dateStart = $data["date_start"];
|
||||
$action = $withAction? $data["action"]: null;
|
||||
$dateStop = $data["date_stop"];
|
||||
$exitcode = $data["exitcode"];
|
||||
if ($action !== null) {
|
||||
$date ??= $data["action_date_step"];
|
||||
$date ??= $data["action_date_start"];
|
||||
if ($date !== null) $action = "$date $action";
|
||||
$action = "Etape en cours: $action";
|
||||
$current = $data["action_current_step"];
|
||||
$max = $data["action_max_step"];
|
||||
if ($current !== null && $max !== null) {
|
||||
$action .= " ($current / $max)";
|
||||
} elseif ($current !== null) {
|
||||
$action .= " ($current)";
|
||||
}
|
||||
}
|
||||
if ($exitcode !== null) {
|
||||
$result = ["Code de retour $exitcode"];
|
||||
if ($data["is_reaped"]) $result[] = "reaped";
|
||||
if ($data["is_ack_done"]) $result[] = "acknowledged";
|
||||
$result = join(", ", $result);
|
||||
} else {
|
||||
$result = null;
|
||||
}
|
||||
if (!$this->wasStarted($data)) {
|
||||
$type = "neutral";
|
||||
$haveLog = false;
|
||||
$exitcode = null;
|
||||
$message = [
|
||||
"status" => "$desc: pas encore démarré",
|
||||
];
|
||||
} elseif ($this->isRunning($data)) {
|
||||
$sinceStart = Elapsed::format_since($dateStart);
|
||||
$type = "info";
|
||||
$haveLog = true;
|
||||
$exitcode = null;
|
||||
$message = [
|
||||
"status" => "$desc: EN COURS pid $data[pid]",
|
||||
"started" => "Démarrée depuis $dateStart ($sinceStart)",
|
||||
"action" => $action,
|
||||
];
|
||||
} elseif ($this->isStopped($data)) {
|
||||
$duration = "\nDurée ".Elapsed::format_delay($dateStart, $dateStop);
|
||||
$sinceStop = Elapsed::format_since($dateStop);
|
||||
$haveLog = true;
|
||||
if ($exitcode === null) $type = "warning";
|
||||
elseif ($exitcode === 0) $type = "success";
|
||||
else $type = "danger";
|
||||
$message = [
|
||||
"status" => "$desc: TERMINEE$duration",
|
||||
"stopped" => "Arrêtée $sinceStop le $dateStop",
|
||||
"result" => $result,
|
||||
];
|
||||
} else {
|
||||
$type = "warning";
|
||||
$haveLog = true;
|
||||
$exitcode = null;
|
||||
$message = [
|
||||
"status" => "$desc: ETAT INCONNU",
|
||||
"started" => "Commencée le $dateStart",
|
||||
"stopped" => $dateStop? "Arrêtée le $dateStop": null,
|
||||
"exitcode" => $result !== null? "Code de retour $result": null,
|
||||
];
|
||||
}
|
||||
return [
|
||||
"type" => $type,
|
||||
"have_log" => $haveLog,
|
||||
"exitcode" => $exitcode,
|
||||
"message" => array_filter($message),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
namespace nulib\app;
|
||||
|
||||
use nulib\A;
|
||||
use nulib\str;
|
||||
|
||||
class args {
|
||||
/**
|
||||
* transformer une liste d'argument de la forme
|
||||
* - ["myArg" => $value] devient ["--my-arg", "$value"]
|
||||
* - ["myOpt" => true] devient ["--my-opt"]
|
||||
* - ["myOpt" => false] est omis
|
||||
* - les autres valeurs sont prises telles quelles
|
||||
*/
|
||||
static function from_array(?array $array): array {
|
||||
$args = [];
|
||||
if ($array === null) return $args;
|
||||
$index = 0;
|
||||
foreach ($array as $arg => $value) {
|
||||
if ($value === false) continue;
|
||||
if ($arg === $index) {
|
||||
$index++;
|
||||
} else {
|
||||
$arg = str::us2camel($arg);
|
||||
$arg = str::camel2us($arg, false, "-");
|
||||
$arg = str_replace("_", "-", $arg);
|
||||
$args[] = "--$arg";
|
||||
if (is_array($value)) $value[] = "--";
|
||||
elseif ($value === true) $value = null;
|
||||
}
|
||||
if (is_array($value)) {
|
||||
A::merge($args, array_map("strval", $value));
|
||||
} elseif ($value !== null) {
|
||||
$args[] = "$value";
|
||||
}
|
||||
}
|
||||
return $args;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
# script à inclure pour implémenter .launcher.php
|
||||
# les constantes suivantes doivent être définies AVANT de chager ce script:
|
||||
# - NULIB_APP_app_params : paramètres du projet
|
||||
|
||||
use nulib\os\path;
|
||||
use nulib\app;
|
||||
|
||||
if ($argc <= 1) die("invalid arguments");
|
||||
|
||||
app::init(NULIB_APP_app_params);
|
||||
|
||||
$app = $argv[1];
|
||||
array_splice($argv, 0, 1); $argc--;
|
||||
if (class_exists($app)) {
|
||||
# la configuration est celle actuellement chargée
|
||||
$app::run();
|
||||
} elseif (is_executable($app)) {
|
||||
# la configuration est passée par une variable d'environnement
|
||||
app::params_putenv();
|
||||
pcntl_exec($app, array_slice($argv, 1));
|
||||
} else {
|
||||
# la configuration est celle actuellement chargée
|
||||
$name = preg_replace('/\.php$/', "", path::basename($app));
|
||||
app::init([
|
||||
"name" => $name,
|
||||
]);
|
||||
require $app;
|
||||
}
|
|
@ -1,136 +0,0 @@
|
|||
<?php
|
||||
namespace nulib\app;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\file\TmpfileWriter;
|
||||
use nulib\os\path;
|
||||
use nulib\os\proc\Cmd;
|
||||
use nulib\output\msg;
|
||||
use nulib\StateException;
|
||||
use nulib\str;
|
||||
use nulib\wip\app\app;
|
||||
|
||||
class launcher {
|
||||
/**
|
||||
* transformer une liste d'argument de la forme
|
||||
* - ["myArg" => $value] devient ["--my-arg", "$value"]
|
||||
* - ["myOpt" => true] devient ["--my-opt"]
|
||||
* - ["myOpt" => false] est momis
|
||||
* - les valeurs séquentielles sont prises telles quelles
|
||||
*/
|
||||
static function verifix_args(array $args): array {
|
||||
if (!cl::is_list($args)) {
|
||||
$fixedArgs = [];
|
||||
$index = 0;
|
||||
foreach ($args as $arg => $value) {
|
||||
if ($arg === $index) {
|
||||
$index++;
|
||||
$fixedArgs[] = $value;
|
||||
continue;
|
||||
} elseif ($value === false) {
|
||||
continue;
|
||||
}
|
||||
$arg = str::us2camel($arg);
|
||||
$arg = str::camel2us($arg, false, "-");
|
||||
$arg = str_replace("_", "-", $arg);
|
||||
$fixedArgs[] = "--$arg";
|
||||
if ($value !== true) $fixedArgs[] = "$value";
|
||||
}
|
||||
$args = $fixedArgs;
|
||||
}
|
||||
# corriger le chemin de l'application pour qu'il soit absolu et normalisé
|
||||
$args[0] = path::abspath($args[0]);
|
||||
return $args;
|
||||
}
|
||||
|
||||
static function launch(string $appClass, array $args): int {
|
||||
$app = app::get();
|
||||
$vendorBindir = $app->getVendorbindir();
|
||||
$launch_php = "$vendorBindir/_launch.php";
|
||||
if (!file_exists($launch_php)) {
|
||||
$launch_php = __DIR__."/../../lib/_launch.php";
|
||||
}
|
||||
$tmpfile = new TmpfileWriter();
|
||||
$tmpfile->keep()->serialize($app->getParams());
|
||||
|
||||
$args = self::verifix_args($args);
|
||||
$cmd = new Cmd([
|
||||
$launch_php,
|
||||
"--internal-use", $tmpfile->getFile(),
|
||||
$appClass, "--", ...$args,
|
||||
]);
|
||||
$cmd->addRedir("both", "/tmp/nulib_app_launcher-launch.log");
|
||||
$cmd->passthru($exitcode);
|
||||
|
||||
# attendre un peu que la commande aie le temps de s'initialiser
|
||||
sleep(1);
|
||||
|
||||
$tmpfile->close();
|
||||
return $exitcode;
|
||||
}
|
||||
|
||||
static function _start(array $args, Runfile $runfile): bool {
|
||||
if ($runfile->warnIfLocked()) return false;
|
||||
$pid = pcntl_fork();
|
||||
if ($pid == -1) {
|
||||
# parent, impossible de forker
|
||||
throw new StateException("unable to fork");
|
||||
} elseif ($pid) {
|
||||
# parent, fork ok
|
||||
return true;
|
||||
} else {
|
||||
## child, fork ok
|
||||
# Créer un groupe de process, pour pouvoir tuer tous les enfants en même temps
|
||||
$runfile->tm_startPg();
|
||||
$logfile = $runfile->getLogfile() ?? "/tmp/nulib_app_launcher-_start.log";
|
||||
$pid = posix_getpid();
|
||||
$exitcode = -776;
|
||||
try {
|
||||
# puis lancer la commande
|
||||
$cmd = new Cmd($args);
|
||||
$cmd->addSource("/g/init.env");
|
||||
$cmd->addRedir("both", $logfile, true);
|
||||
msg::debug("$pid: launching\n".$cmd->getCmd());
|
||||
$cmd->fork_exec($exitcode);
|
||||
msg::debug("$pid: exitcode=$exitcode");
|
||||
return true;
|
||||
} finally {
|
||||
$runfile->wfStopped($exitcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function _stop(Runfile $runfile): void {
|
||||
$data = $runfile->read();
|
||||
$pid = $data["pg_pid"];
|
||||
if ($pid === null) {
|
||||
msg::warning("$data[name]: groupe de process inconnu");
|
||||
return;
|
||||
}
|
||||
msg::action("kill $pid");
|
||||
if (!posix_kill(-$pid, SIGKILL)) {
|
||||
switch (posix_get_last_error()) {
|
||||
case PCNTL_ESRCH:
|
||||
msg::afailure("process inexistant");
|
||||
break;
|
||||
case PCNTL_EPERM:
|
||||
msg::afailure("process non accessible");
|
||||
break;
|
||||
case PCNTL_EINVAL:
|
||||
msg::afailure("signal invalide");
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
$timeout = 10;
|
||||
while ($runfile->tm_isUndead($pid)) {
|
||||
sleep(1);
|
||||
if (--$timeout == 0) {
|
||||
msg::afailure("impossible d'arrêter la tâche");
|
||||
return;
|
||||
}
|
||||
}
|
||||
$runfile->wfStopped(-778);
|
||||
msg::asuccess();
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
namespace nulib;
|
||||
|
||||
use ArrayAccess;
|
||||
use nulib\php\func;
|
||||
use nulib\php\nur_func;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
|
@ -28,6 +28,20 @@ class cl {
|
|||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* construire un tableau avec le résultat de $row[$key] pour chaque élément
|
||||
* de $rows
|
||||
*/
|
||||
static function all_get($key, ?iterable $rows): array {
|
||||
$array = [];
|
||||
if ($rows !== null) {
|
||||
foreach ($rows as $row) {
|
||||
$array[] = self::get($row, $key);
|
||||
}
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* retourner la première valeur de $array ou $default si le tableau est null
|
||||
* ou vide
|
||||
|
@ -322,7 +336,10 @@ class cl {
|
|||
static final function merge2(...$arrays): ?array {
|
||||
$merged = null;
|
||||
foreach ($arrays as $array) {
|
||||
foreach (self::with($array) as $key => $value) {
|
||||
$array = self::withn($array);
|
||||
if ($array === null) continue;
|
||||
$merged ??= [];
|
||||
foreach ($array as $key => $value) {
|
||||
$merged[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
@ -334,9 +351,9 @@ class cl {
|
|||
static final function map(callable $callback, ?iterable $array): array {
|
||||
$result = [];
|
||||
if ($array !== null) {
|
||||
$ctx = func::_prepare($callback);
|
||||
$ctx = nur_func::_prepare($callback);
|
||||
foreach ($array as $key => $value) {
|
||||
$result[$key] = func::_call($ctx, [$value, $key]);
|
||||
$result[$key] = nur_func::_call($ctx, [$value, $key]);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
|
@ -710,15 +727,12 @@ class cl {
|
|||
#############################################################################
|
||||
|
||||
static final function sorted(?array $array, int $flags=SORT_REGULAR, bool $assoc=false): ?array {
|
||||
if ($array === null) return null;
|
||||
if ($assoc) asort($array, $flags);
|
||||
else sort($array, $flags);
|
||||
A::sort($array, $flags, $assoc);
|
||||
return $array;
|
||||
}
|
||||
|
||||
static final function ksorted(?array $array, int $flags=SORT_REGULAR): ?array {
|
||||
if ($array === null) return null;
|
||||
ksort($array, $flags);
|
||||
A::ksort($array, $flags);
|
||||
return $array;
|
||||
}
|
||||
|
||||
|
@ -749,9 +763,7 @@ class cl {
|
|||
}
|
||||
|
||||
static final function usorted(?array $array, array $keys, bool $assoc=false): ?array {
|
||||
if ($array === null) return null;
|
||||
if ($assoc) uasort($array, self::compare($keys));
|
||||
else usort($array, self::compare($keys));
|
||||
A::usort($array, $keys, $assoc);
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
namespace nulib\db;
|
||||
|
||||
use nulib\php\func;
|
||||
use nulib\php\nur_func;
|
||||
use nulib\ValueException;
|
||||
use Traversable;
|
||||
|
||||
|
@ -87,7 +87,7 @@ class Capacitor implements ITransactor {
|
|||
if ($func !== null) {
|
||||
$commited = false;
|
||||
try {
|
||||
func::call($func, $this);
|
||||
nur_func::call($func, $this);
|
||||
if ($commit) {
|
||||
$this->commit();
|
||||
$commited = true;
|
||||
|
|
|
@ -27,7 +27,7 @@ class CapacitorChannel {
|
|||
if ($name !== null) {
|
||||
$name = strtolower($name);
|
||||
if ($tableName === null) {
|
||||
$tableName = str_replace("-", "_", $tableName) . "_channel";
|
||||
$tableName = str_replace("-", "_", $name) . "_channel";
|
||||
}
|
||||
} else {
|
||||
$name = static::class;
|
||||
|
|
|
@ -3,7 +3,7 @@ namespace nulib\db;
|
|||
|
||||
use nulib\cl;
|
||||
use nulib\db\cache\cache;
|
||||
use nulib\php\func;
|
||||
use nulib\php\nur_func;
|
||||
use nulib\ValueException;
|
||||
use Traversable;
|
||||
|
||||
|
@ -154,10 +154,14 @@ EOT;
|
|||
return $this->_getCreateSql($this->getChannel($channel));
|
||||
}
|
||||
|
||||
protected function _afterCreate(CapacitorChannel $channel): void {
|
||||
}
|
||||
|
||||
protected function _create(CapacitorChannel $channel): void {
|
||||
$channel->ensureSetup();
|
||||
if (!$channel->isCreated()) {
|
||||
$this->db->exec($this->_createSql($channel));
|
||||
$this->_afterCreate($channel);
|
||||
$channel->setCreated();
|
||||
}
|
||||
}
|
||||
|
@ -178,8 +182,12 @@ EOT;
|
|||
$this->_ensureExists($this->getChannel($channel));
|
||||
}
|
||||
|
||||
protected function _beforeReset(CapacitorChannel $channel): void {
|
||||
}
|
||||
|
||||
/** supprimer le canal spécifié */
|
||||
function _reset(CapacitorChannel $channel, bool $recreate=false): void {
|
||||
$this->_beforeReset($channel);
|
||||
$this->db->exec([
|
||||
"drop table if exists",
|
||||
$channel->getTableName(),
|
||||
|
@ -224,8 +232,8 @@ EOT;
|
|||
|
||||
$initFunc = [$channel, "getItemValues"];
|
||||
$initArgs = $args;
|
||||
func::ensure_func($initFunc, null, $initArgs);
|
||||
$values = func::call($initFunc, $item, ...$initArgs);
|
||||
nur_func::ensure_func($initFunc, null, $initArgs);
|
||||
$values = nur_func::call($initFunc, $item, ...$initArgs);
|
||||
if ($values === [false]) return 0;
|
||||
|
||||
$row = cl::merge(
|
||||
|
@ -253,7 +261,7 @@ EOT;
|
|||
$insert = true;
|
||||
$initFunc = [$channel, "onCreate"];
|
||||
$initArgs = $args;
|
||||
func::ensure_func($initFunc, null, $initArgs);
|
||||
nur_func::ensure_func($initFunc, null, $initArgs);
|
||||
$values = $this->unserialize($channel, $row);
|
||||
$pvalues = null;
|
||||
} else {
|
||||
|
@ -270,12 +278,12 @@ EOT;
|
|||
}
|
||||
$initFunc = [$channel, "onUpdate"];
|
||||
$initArgs = $args;
|
||||
func::ensure_func($initFunc, null, $initArgs);
|
||||
nur_func::ensure_func($initFunc, null, $initArgs);
|
||||
$values = $this->unserialize($channel, $row);
|
||||
$pvalues = $this->unserialize($channel, $prow);
|
||||
}
|
||||
|
||||
$updates = func::call($initFunc, $item, $values, $pvalues, ...$initArgs);
|
||||
$updates = nur_func::call($initFunc, $item, $values, $pvalues, ...$initArgs);
|
||||
if ($updates === [false]) return 0;
|
||||
if (is_array($updates) && $updates) {
|
||||
if ($insert === null) $insert = false;
|
||||
|
@ -287,8 +295,8 @@ EOT;
|
|||
}
|
||||
|
||||
if ($func !== null) {
|
||||
func::ensure_func($func, $channel, $args);
|
||||
$updates = func::call($func, $item, $values, $pvalues, ...$args);
|
||||
nur_func::ensure_func($func, $channel, $args);
|
||||
$updates = nur_func::call($func, $item, $values, $pvalues, ...$args);
|
||||
if ($updates === [false]) return 0;
|
||||
if (is_array($updates) && $updates) {
|
||||
if ($insert === null) $insert = false;
|
||||
|
@ -502,8 +510,8 @@ EOT;
|
|||
function _each(CapacitorChannel $channel, $filter, $func, ?array $args, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
|
||||
$this->_create($channel);
|
||||
if ($func === null) $func = CapacitorChannel::onEach;
|
||||
func::ensure_func($func, $channel, $args);
|
||||
$onEach = func::_prepare($func);
|
||||
nur_func::ensure_func($func, $channel, $args);
|
||||
$onEach = nur_func::_prepare($func);
|
||||
$db = $this->db();
|
||||
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
||||
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
||||
|
@ -520,7 +528,7 @@ EOT;
|
|||
$all = $this->_allCached("each", $channel, $filter, $mergeQuery);
|
||||
foreach ($all as $values) {
|
||||
$rowIds = $this->getRowIds($channel, $values);
|
||||
$updates = func::_call($onEach, [$values["item"], $values, ...$args]);
|
||||
$updates = nur_func::_call($onEach, [$values["item"], $values, ...$args]);
|
||||
if (is_array($updates) && $updates) {
|
||||
if (!array_key_exists("modified_", $updates)) {
|
||||
$updates["modified_"] = date("Y-m-d H:i:s");
|
||||
|
@ -571,8 +579,8 @@ EOT;
|
|||
function _delete(CapacitorChannel $channel, $filter, $func, ?array $args): int {
|
||||
$this->_create($channel);
|
||||
if ($func === null) $func = CapacitorChannel::onDelete;
|
||||
func::ensure_func($func, $channel, $args);
|
||||
$onEach = func::_prepare($func);
|
||||
nur_func::ensure_func($func, $channel, $args);
|
||||
$onEach = nur_func::_prepare($func);
|
||||
$db = $this->db();
|
||||
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
||||
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
||||
|
@ -588,7 +596,7 @@ EOT;
|
|||
$all = $this->_allCached("delete", $channel, $filter);
|
||||
foreach ($all as $values) {
|
||||
$rowIds = $this->getRowIds($channel, $values);
|
||||
$delete = boolval(func::_call($onEach, [$values["item"], $values, ...$args]));
|
||||
$delete = boolval(nur_func::_call($onEach, [$values["item"], $values, ...$args]));
|
||||
if ($delete) {
|
||||
$db->exec([
|
||||
"delete",
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
namespace nulib\db\_private;
|
||||
|
||||
use DateTimeInterface;
|
||||
use nulib\php\time\Date;
|
||||
use nulib\php\time\DateTime;
|
||||
use nulib\str;
|
||||
|
||||
trait Tbindings {
|
||||
static function is_sqldate(string $date): bool {
|
||||
return preg_match('/^\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2}:\d{2})?$/', $date);
|
||||
}
|
||||
|
||||
protected function verifixBindings(&$value): void {
|
||||
if ($value instanceof Date) {
|
||||
$value = $value->format('Y-m-d');
|
||||
} elseif ($value instanceof DateTime) {
|
||||
$value = $value->format('Y-m-d H:i:s');
|
||||
} elseif ($value instanceof DateTimeInterface) {
|
||||
$value = $value->format('Y-m-d H:i:s');
|
||||
str::del_suffix($value, " 00:00:00");
|
||||
} elseif (is_string($value)) {
|
||||
if (self::is_sqldate($value)) {
|
||||
# déjà dans le bon format
|
||||
} elseif (Date::isa_date($value, true)) {
|
||||
$value = new Date($value);
|
||||
$value = $value->format('Y-m-d');
|
||||
} elseif (DateTime::isa_datetime($value, true)) {
|
||||
$value = new DateTime($value);
|
||||
$value = $value->format('Y-m-d H:i:s');
|
||||
}
|
||||
} elseif (is_bool($value)) {
|
||||
$value = $value? 1: 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,8 +22,8 @@ trait Tinsert {
|
|||
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
|
||||
|
||||
## insert
|
||||
self::consume('insert\s*', $tmpsql);
|
||||
$sql[] = "insert";
|
||||
self::consume('(insert(?:\s+or\s+(?:ignore|replace))?)\s*', $tmpsql, $ms);
|
||||
$sql[] = $ms[1];
|
||||
|
||||
## into
|
||||
self::consume('into\s*', $tmpsql);
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
namespace nulib\db\_private;
|
||||
|
||||
use nulib\php\time\Date;
|
||||
use nulib\php\time\DateTime;
|
||||
|
||||
trait Tvalues {
|
||||
/**
|
||||
* Tester si $date est une date/heure valide de la forme "YYYY-mm-dd HH:MM:SS"
|
||||
*
|
||||
* Si oui, $ms obtient les 6 éléments de la chaine
|
||||
*/
|
||||
static function is_datetime($date, ?array &$ms=null): bool {
|
||||
return is_string($date) && preg_match('/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/', $date, $ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tester si $date est une date valide de la forme "YYYY-mm-dd [00:00:00]"
|
||||
*
|
||||
* Si oui, $ms obtient les 3 éléments de la chaine
|
||||
*/
|
||||
static function is_date($date, ?array &$ms=null): bool {
|
||||
return is_string($date) && preg_match('/^(\d{4})-(\d{2})-(\d{2})(?: 00:00:00)?$/', $date, $ms);
|
||||
}
|
||||
|
||||
function verifixRow(array &$row) {
|
||||
foreach ($row as &$value) {
|
||||
if (self::is_date($value)) {
|
||||
$value = new Date($value);
|
||||
} elseif (self::is_datetime($value)) {
|
||||
$value = new DateTime($value);
|
||||
}
|
||||
}; unset($value);
|
||||
}
|
||||
}
|
|
@ -3,14 +3,17 @@ namespace nulib\db\pdo;
|
|||
|
||||
use Generator;
|
||||
use nulib\cl;
|
||||
use nulib\db\_private\Tvalues;
|
||||
use nulib\db\IDatabase;
|
||||
use nulib\db\ITransactor;
|
||||
use nulib\php\func;
|
||||
use nulib\php\nur_func;
|
||||
use nulib\php\time\Date;
|
||||
use nulib\php\time\DateTime;
|
||||
use nulib\ValueException;
|
||||
|
||||
class Pdo implements IDatabase {
|
||||
use Tvalues;
|
||||
|
||||
static function with($pdo, ?array $params=null): self {
|
||||
if ($pdo instanceof static) {
|
||||
return $pdo;
|
||||
|
@ -118,8 +121,8 @@ class Pdo implements IDatabase {
|
|||
$dbconn = $this->dbconn;
|
||||
$options = $this->options;
|
||||
if (is_callable($options)) {
|
||||
func::ensure_func($options, $this, $args);
|
||||
$options = func::call($options, ...$args);
|
||||
nur_func::ensure_func($options, $this, $args);
|
||||
$options = nur_func::call($options, ...$args);
|
||||
}
|
||||
$this->db = new \PDO($dbconn["name"], $dbconn["user"], $dbconn["pass"], $options);
|
||||
_config::with($this->config)->configure($this);
|
||||
|
@ -190,7 +193,7 @@ class Pdo implements IDatabase {
|
|||
if ($func !== null) {
|
||||
$commited = false;
|
||||
try {
|
||||
func::call($func, $this);
|
||||
nur_func::call($func, $this);
|
||||
if ($commit) {
|
||||
$this->commit();
|
||||
$commited = true;
|
||||
|
@ -219,34 +222,6 @@ class Pdo implements IDatabase {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tester si $date est une date/heure valide de la forme "YYYY-mm-dd HH:MM:SS"
|
||||
*
|
||||
* Si oui, $ms obtient les 6 éléments de la chaine
|
||||
*/
|
||||
static function is_datetime($date, ?array &$ms=null): bool {
|
||||
return is_string($date) && preg_match('/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/', $date, $ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tester si $date est une date valide de la forme "YYYY-mm-dd [00:00:00]"
|
||||
*
|
||||
* Si oui, $ms obtient les 3 éléments de la chaine
|
||||
*/
|
||||
static function is_date($date, ?array &$ms=null): bool {
|
||||
return is_string($date) && preg_match('/^(\d{4})-(\d{2})-(\d{2})(?: 00:00:00)?$/', $date, $ms);
|
||||
}
|
||||
|
||||
function verifixRow(array &$row) {
|
||||
foreach ($row as &$value) {
|
||||
if (self::is_date($value)) {
|
||||
$value = new Date($value);
|
||||
} elseif (self::is_datetime($value)) {
|
||||
$value = new DateTime($value);
|
||||
}
|
||||
}; unset($value);
|
||||
}
|
||||
|
||||
function get($query, ?array $params=null, bool $entireRow=false) {
|
||||
$db = $this->db();
|
||||
$query = new _query_base($query, $params);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
namespace nulib\db\pdo;
|
||||
|
||||
use nulib\php\func;
|
||||
use nulib\php\nur_func;
|
||||
|
||||
class _config {
|
||||
static function with($configs): self {
|
||||
|
@ -25,11 +25,11 @@ class _config {
|
|||
|
||||
function configure(Pdo $pdo): void {
|
||||
foreach ($this->configs as $key => $config) {
|
||||
if (is_string($config) && !func::is_method($config)) {
|
||||
if (is_string($config) && !nur_func::is_method($config)) {
|
||||
$pdo->exec($config);
|
||||
} else {
|
||||
func::ensure_func($config, $this, $args);
|
||||
func::call($config, $pdo, $key, ...$args);
|
||||
nur_func::ensure_func($config, $this, $args);
|
||||
nur_func::call($config, $pdo, $key, ...$args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
<?php
|
||||
namespace nulib\db\pdo;
|
||||
|
||||
use DateTimeInterface;
|
||||
use nulib\db\_private\_base;
|
||||
use nulib\php\time\Date;
|
||||
use nulib\php\time\DateTime;
|
||||
use nulib\str;
|
||||
use nulib\db\_private\Tbindings;
|
||||
use nulib\ValueException;
|
||||
|
||||
class _query_base extends _base {
|
||||
use Tbindings;
|
||||
|
||||
protected static function verifix(&$sql, ?array &$bindinds=null, ?array &$meta=null): void {
|
||||
if (is_array($sql)) {
|
||||
$prefix = $sql[0] ?? null;
|
||||
|
@ -55,33 +54,6 @@ class _query_base extends _base {
|
|||
}
|
||||
}
|
||||
|
||||
static function is_sqldate(string $date): bool {
|
||||
return preg_match('/^\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2}:\d{2})?$/', $date);
|
||||
}
|
||||
|
||||
protected function verifixBindings(&$value): void {
|
||||
if ($value instanceof Date) {
|
||||
$value = $value->format('Y-m-d');
|
||||
} elseif ($value instanceof DateTime) {
|
||||
$value = $value->format('Y-m-d H:i:s');
|
||||
} elseif ($value instanceof DateTimeInterface) {
|
||||
$value = $value->format('Y-m-d H:i:s');
|
||||
str::del_suffix($value, " 00:00:00");
|
||||
} elseif (is_string($value)) {
|
||||
if (self::is_sqldate($value)) {
|
||||
# déjà dans le bon format
|
||||
} elseif (Date::isa_date($value, true)) {
|
||||
$value = new Date($value);
|
||||
$value = $value->format('Y-m-d');
|
||||
} elseif (DateTime::isa_datetime($value, true)) {
|
||||
$value = new DateTime($value);
|
||||
$value = $value->format('Y-m-d H:i:s');
|
||||
}
|
||||
} elseif (is_bool($value)) {
|
||||
$value = $value? 1: 0;
|
||||
}
|
||||
}
|
||||
|
||||
const DEBUG_QUERIES = false;
|
||||
|
||||
function useStmt(\PDO $db, ?\PDOStatement &$stmt=null, ?string &$sql=null): bool {
|
||||
|
|
|
@ -3,9 +3,10 @@ namespace nulib\db\sqlite;
|
|||
|
||||
use Generator;
|
||||
use nulib\cl;
|
||||
use nulib\db\_private\Tvalues;
|
||||
use nulib\db\IDatabase;
|
||||
use nulib\db\ITransactor;
|
||||
use nulib\php\func;
|
||||
use nulib\php\nur_func;
|
||||
use nulib\ValueException;
|
||||
use SQLite3;
|
||||
use SQLite3Result;
|
||||
|
@ -15,6 +16,8 @@ use SQLite3Stmt;
|
|||
* Class Sqlite: frontend vers une base de données sqlite3
|
||||
*/
|
||||
class Sqlite implements IDatabase {
|
||||
use Tvalues;
|
||||
|
||||
static function with($sqlite, ?array $params=null): self {
|
||||
if ($sqlite instanceof static) {
|
||||
return $sqlite;
|
||||
|
@ -234,7 +237,7 @@ class Sqlite implements IDatabase {
|
|||
if ($func !== null) {
|
||||
$commited = false;
|
||||
try {
|
||||
func::call($func, $this);
|
||||
nur_func::call($func, $this);
|
||||
if ($commit) {
|
||||
$this->commit();
|
||||
$commited = true;
|
||||
|
@ -278,7 +281,8 @@ class Sqlite implements IDatabase {
|
|||
try {
|
||||
$row = $result->fetchArray(SQLITE3_ASSOC);
|
||||
if ($row === false) return null;
|
||||
elseif ($entireRow) return $row;
|
||||
$this->verifixRow($row);
|
||||
if ($entireRow) return $row;
|
||||
else return cl::first($row);
|
||||
} finally {
|
||||
$result->finalize();
|
||||
|
@ -299,6 +303,7 @@ class Sqlite implements IDatabase {
|
|||
if ($primaryKeys !== null) $primaryKeys = cl::with($primaryKeys);
|
||||
try {
|
||||
while (($row = $result->fetchArray(SQLITE3_ASSOC)) !== false) {
|
||||
$this->verifixRow($row);
|
||||
if ($primaryKeys !== null) {
|
||||
$key = implode("-", cl::select($row, $primaryKeys));
|
||||
yield $key => $row;
|
||||
|
|
|
@ -28,14 +28,67 @@ class SqliteStorage extends CapacitorStorage {
|
|||
return self::format_sql($channel, $query->getSql());
|
||||
}
|
||||
|
||||
function _exists(CapacitorChannel $channel): bool {
|
||||
$tableName = $this->db->get([
|
||||
"select name from sqlite_schema",
|
||||
function tableExists(string $tableName): bool {
|
||||
$name = $this->db->get([
|
||||
# depuis la version 3.33.0 le nom officiel de la table est sqlite_schema,
|
||||
# mais le nom sqlite_master est toujours valable pour le moment
|
||||
"select name from sqlite_master ",
|
||||
"where" => ["name" => $tableName],
|
||||
]);
|
||||
return $name !== null;
|
||||
}
|
||||
|
||||
function channelExists(string $name): bool {
|
||||
$name = $this->db->get([
|
||||
"select name from _channels",
|
||||
"where" => ["name" => $name],
|
||||
]);
|
||||
return $name !== null;
|
||||
}
|
||||
|
||||
protected function _afterCreate(CapacitorChannel $channel): void {
|
||||
$db = $this->db;
|
||||
if (!$this->tableExists("_channels")) {
|
||||
# ne pas créer si la table existe déjà, pour éviter d'avoir besoin d'un
|
||||
# verrou en écriture
|
||||
$db->exec([
|
||||
"create table if not exists",
|
||||
"table" => "_channels",
|
||||
"cols" => [
|
||||
"name" => "varchar primary key",
|
||||
"table_name" => "varchar",
|
||||
"class" => "varchar",
|
||||
],
|
||||
]);
|
||||
}
|
||||
if (!$this->channelExists($channel->getName())) {
|
||||
# ne pas insérer si la ligne existe déjà, pour éviter d'avoir besoin d'un
|
||||
# verrou en écriture
|
||||
$db->exec([
|
||||
"insert",
|
||||
"into" => "_channels",
|
||||
"values" => [
|
||||
"name" => $channel->getName(),
|
||||
"table_name" => $channel->getTableName(),
|
||||
"class" => get_class($channel),
|
||||
],
|
||||
"suffix" => "on conflict do nothing",
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function _beforeReset(CapacitorChannel $channel): void {
|
||||
$this->db->exec([
|
||||
"delete",
|
||||
"from" => "_channels",
|
||||
"where" => [
|
||||
"name" => $channel->getTableName(),
|
||||
"name" => $channel->getName(),
|
||||
],
|
||||
]);
|
||||
return $tableName !== null;
|
||||
}
|
||||
|
||||
function _exists(CapacitorChannel $channel): bool {
|
||||
return $this->tableExists($channel->getTableName());
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\php\func;
|
||||
use nulib\php\nur_func;
|
||||
|
||||
class _config {
|
||||
static function with($configs): self {
|
||||
|
@ -25,11 +25,11 @@ class _config {
|
|||
|
||||
function configure(Sqlite $sqlite): void {
|
||||
foreach ($this->configs as $key => $config) {
|
||||
if (is_string($config) && !func::is_method($config)) {
|
||||
if (is_string($config) && !nur_func::is_method($config)) {
|
||||
$sqlite->exec($config);
|
||||
} else {
|
||||
func::ensure_func($config, $this, $args);
|
||||
func::call($config, $sqlite, $key, ...$args);
|
||||
nur_func::ensure_func($config, $this, $args);
|
||||
nur_func::call($config, $sqlite, $key, ...$args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\php\func;
|
||||
use nulib\php\nur_func;
|
||||
|
||||
class _migration {
|
||||
static function with($migrations): self {
|
||||
|
@ -40,11 +40,11 @@ class _migration {
|
|||
"value" => $migration,
|
||||
"done" => 0,
|
||||
]);
|
||||
if (is_string($migration) && !func::is_method($migration)) {
|
||||
if (is_string($migration) && !nur_func::is_method($migration)) {
|
||||
$sqlite->exec($migration);
|
||||
} else {
|
||||
func::ensure_func($migration, $this, $args);
|
||||
func::call($migration, $sqlite, $key, ...$args);
|
||||
nur_func::ensure_func($migration, $this, $args);
|
||||
nur_func::call($migration, $sqlite, $key, ...$args);
|
||||
}
|
||||
$sqlite->exec("update _migration set done = 1 where key = :key", [
|
||||
"key" => $key,
|
||||
|
|
|
@ -2,11 +2,14 @@
|
|||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\db\_private\_base;
|
||||
use nulib\db\_private\Tbindings;
|
||||
use nulib\ValueException;
|
||||
use SQLite3;
|
||||
use SQLite3Stmt;
|
||||
|
||||
class _query_base extends _base {
|
||||
use Tbindings;
|
||||
|
||||
protected static function verifix(&$sql, ?array &$bindinds=null, ?array &$meta=null): void {
|
||||
if (is_array($sql)) {
|
||||
$prefix = $sql[0] ?? null;
|
||||
|
@ -42,6 +45,7 @@ class _query_base extends _base {
|
|||
$close = true;
|
||||
try {
|
||||
foreach ($this->bindings as $param => $value) {
|
||||
$this->verifixBindings($value);
|
||||
SqliteException::check($db, $stmt->bindValue($param, $value));
|
||||
}
|
||||
$close = false;
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
namespace nulib\ext;
|
||||
|
||||
use Exception;
|
||||
use nulib\ext\json\JsonException;
|
||||
use nulib\file;
|
||||
use nulib\os\IOException;
|
||||
|
||||
/**
|
||||
* Class json: gestion de données json
|
||||
*/
|
||||
class json {
|
||||
static function decode(string $json, int $flags=0) {
|
||||
return json_decode($json, true, 512, $flags);
|
||||
}
|
||||
|
||||
static function encode($data, int $flags=0): string {
|
||||
$flags |= JSON_UNESCAPED_SLASHES + JSON_UNESCAPED_UNICODE;
|
||||
return json_encode($data, $flags);
|
||||
}
|
||||
|
||||
static function check(string $json, &$data=null): bool {
|
||||
try {
|
||||
$data = self::decode($json, JSON_THROW_ON_ERROR);
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IOException si une erreur de lecture s'est produite
|
||||
*/
|
||||
static final function load($input): array {
|
||||
$contents = file::reader($input)->getContents();
|
||||
return JsonException::ensure_json_value(self::decode($contents));
|
||||
}
|
||||
|
||||
/** obtenir la valeur JSON correspondant au corps de la requête POST */
|
||||
static final function post_data() {
|
||||
$content = file_get_contents("php://input");
|
||||
return JsonException::ensure_json_value(self::decode($content));
|
||||
}
|
||||
|
||||
/** envoyer $data au format JSON */
|
||||
static final function send($data, bool $exit=true): void {
|
||||
header("Content-Type: application/json");
|
||||
print self::encode($data);
|
||||
if ($exit) exit;
|
||||
}
|
||||
|
||||
const INDENT_TABS = "\t";
|
||||
|
||||
static final function with($data, ?string $indent=null): string {
|
||||
$json = self::encode($data, JSON_PRETTY_PRINT);
|
||||
if ($indent !== null) {
|
||||
$json = preg_replace_callback('/^(?: {4})+/m', function (array $ms) use ($indent) {
|
||||
return str_repeat($indent, strlen($ms[0]) / 4);
|
||||
}, $json);
|
||||
}
|
||||
return $json;
|
||||
}
|
||||
|
||||
static final function dump($data, $output=null): void {
|
||||
file::writer($output)->putContents(self::with($data));
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace nulib\php\json;
|
||||
namespace nulib\ext\json;
|
||||
|
||||
use RuntimeException;
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
<?php
|
||||
namespace nulib\ext\spreadsheet;
|
||||
|
||||
use nulib\file\csv\AbstractBuilder;
|
||||
use nulib\file\csv\TAbstractBuilder;
|
||||
use nulib\os\path;
|
||||
use nulib\web\http;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\IValueBinder;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\StringValueBinder;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Ods;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
|
||||
class PhpSpreadsheetBuilder extends AbstractBuilder {
|
||||
use TAbstractBuilder;
|
||||
|
||||
/** @var string|int|null nom de la feuille dans laquelle écrire */
|
||||
const WSNAME = null;
|
||||
|
||||
function __construct(?string $output, ?array $params=null) {
|
||||
parent::__construct($output, $params);
|
||||
$this->ss = new Spreadsheet();
|
||||
$this->valueBinder = new StringValueBinder();
|
||||
$this->setWsname($params["wsname"] ?? static::WSNAME);
|
||||
}
|
||||
|
||||
protected Spreadsheet $ss;
|
||||
|
||||
protected IValueBinder $valueBinder;
|
||||
|
||||
protected ?Worksheet $ws;
|
||||
|
||||
protected int $nrow;
|
||||
|
||||
const STYLE_ROW = 0, STYLE_HEADER = 1;
|
||||
|
||||
protected int $rowStyle;
|
||||
|
||||
/**
|
||||
* @param string|int|null $wsname
|
||||
*/
|
||||
function setWsname($wsname): self {
|
||||
$ss = $this->ss;
|
||||
$this->ws = null;
|
||||
$this->nrow = 0;
|
||||
$this->rowStyle = self::STYLE_ROW;
|
||||
|
||||
$ws = wsutils::get_ws($wsname, $ss);
|
||||
if ($ws === null) {
|
||||
$ws = $ss->createSheet()->setTitle($wsname);
|
||||
$this->wroteHeaders = false;
|
||||
} else {
|
||||
$maxRow = wsutils::compute_max_coords($ws)[1];
|
||||
$this->nrow = $maxRow - 1;
|
||||
$this->wroteHeaders = $maxRow > 1;
|
||||
}
|
||||
$this->ws = $ws;
|
||||
return $this;
|
||||
}
|
||||
|
||||
function _write(array $row): void {
|
||||
$ws = $this->ws;
|
||||
$styleHeader = $this->rowStyle === self::STYLE_HEADER;
|
||||
$nrow = ++$this->nrow;
|
||||
$ncol = 1;
|
||||
foreach ($row as $col) {
|
||||
$ws->getCellByColumnAndRow($ncol++, $nrow)->setValue($col, $this->valueBinder);
|
||||
}
|
||||
if ($styleHeader) {
|
||||
$ws->getStyle("$nrow:$nrow")->getFont()->setBold(true);
|
||||
$maxcol = count($row);
|
||||
for ($ncol = 1; $ncol <= $maxcol; $ncol++) {
|
||||
$ws->getColumnDimensionByColumn($ncol)->setAutoSize(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function writeHeaders(?array $headers=null): void {
|
||||
$this->rowStyle = self::STYLE_HEADER;
|
||||
parent::writeHeaders($headers);
|
||||
$this->rowStyle = self::STYLE_ROW;
|
||||
}
|
||||
|
||||
function _sendContentType(): void {
|
||||
switch (path::ext($this->output)) {
|
||||
case ".ods":
|
||||
$contentType = "application/vnd.oasis.opendocument.spreadsheet";
|
||||
break;
|
||||
case ".xlsx":
|
||||
default:
|
||||
$contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||
break;
|
||||
}
|
||||
http::content_type($contentType);
|
||||
}
|
||||
|
||||
protected function _checkOk(): bool {
|
||||
switch (path::ext($this->output)) {
|
||||
case ".ods":
|
||||
$writer = new Ods($this->ss);
|
||||
break;
|
||||
case ".xlsx":
|
||||
default:
|
||||
$writer = new Xlsx($this->ss);
|
||||
break;
|
||||
}
|
||||
$writer->save($this->getResource());
|
||||
$this->rewind();
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
<?php
|
||||
namespace nulib\ext\spreadsheet;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\file\csv\AbstractReader;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\RichText\RichText;
|
||||
|
||||
class PhpSpreadsheetReader extends AbstractReader {
|
||||
const DATETIME_FORMAT = 'dd/mm/yyyy hh:mm:ss';
|
||||
const DATE_FORMAT = 'dd/mm/yyyy';
|
||||
const TIME_FORMAT = 'hh:mm:ss';
|
||||
const FORMAT_MAPPINGS = [
|
||||
'mm/dd hh' => self::DATETIME_FORMAT,
|
||||
'dd/mm hh' => self::DATETIME_FORMAT,
|
||||
'mm/dd hh:mm' => self::DATETIME_FORMAT,
|
||||
'dd/mm hh:mm' => self::DATETIME_FORMAT,
|
||||
'mm/dd hh:mm:ss' => self::DATETIME_FORMAT,
|
||||
'dd/mm hh:mm:ss' => self::DATETIME_FORMAT,
|
||||
'mm/dd/yyyy hh' => self::DATETIME_FORMAT,
|
||||
'dd/mm/yyyy hh' => self::DATETIME_FORMAT,
|
||||
'mm/dd/yyyy hh:mm' => self::DATETIME_FORMAT,
|
||||
'dd/mm/yyyy hh:mm' => self::DATETIME_FORMAT,
|
||||
'mm/dd/yyyy hh:mm:ss' => self::DATETIME_FORMAT,
|
||||
'dd/mm/yyyy hh:mm:ss' => self::DATETIME_FORMAT,
|
||||
'yyyy/mm/dd hh' => self::DATETIME_FORMAT,
|
||||
'yyyy/mm/dd hh:mm' => self::DATETIME_FORMAT,
|
||||
'yyyy/mm/dd hh:mm:ss' => self::DATETIME_FORMAT,
|
||||
|
||||
'mm/dd' => self::DATE_FORMAT,
|
||||
'dd/mm' => self::DATE_FORMAT,
|
||||
'mm/dd/yyyy' => self::DATE_FORMAT,
|
||||
'dd/mm/yyyy' => self::DATE_FORMAT,
|
||||
'yyyy/mm/dd' => self::DATE_FORMAT,
|
||||
'mm/yyyy' => self::DATE_FORMAT,
|
||||
|
||||
'hh AM/PM' => self::TIME_FORMAT,
|
||||
'hh:mm AM/PM' => self::TIME_FORMAT,
|
||||
'hh:mm:ss AM/PM' => self::TIME_FORMAT,
|
||||
'hh' => self::TIME_FORMAT,
|
||||
'hh:mm' => self::TIME_FORMAT,
|
||||
'hh:mm:ss' => self::TIME_FORMAT,
|
||||
'[hh]:mm:ss' => self::TIME_FORMAT,
|
||||
'mm:ss' => self::TIME_FORMAT,
|
||||
];
|
||||
|
||||
/** @var string|int|null nom de la feuille depuis laquelle lire */
|
||||
const WSNAME = null;
|
||||
|
||||
function __construct($input, ?array $params=null) {
|
||||
parent::__construct($input, $params);
|
||||
$this->wsname = $params["wsname"] ?? static::WSNAME;
|
||||
}
|
||||
|
||||
protected $wsname;
|
||||
|
||||
/**
|
||||
* @param string|int|null $wsname
|
||||
*/
|
||||
function setWsname($wsname): self {
|
||||
$this->wsname = $wsname;
|
||||
return $this;
|
||||
}
|
||||
|
||||
function getIterator() {
|
||||
$ss = IOFactory::load($this->input);
|
||||
$ws = wsutils::get_ws($this->wsname, $ss);
|
||||
|
||||
[$nbCols, $nbRows] = wsutils::compute_max_coords($ws);
|
||||
$this->isrc = $this->idest = 0;
|
||||
for ($nrow = 1; $nrow <= $nbRows; $nrow++) {
|
||||
$row = [];
|
||||
for ($ncol = 1; $ncol <= $nbCols; $ncol++) {
|
||||
if ($ws->cellExistsByColumnAndRow($ncol, $nrow)) {
|
||||
$cell = $ws->getCellByColumnAndRow($ncol, $nrow);
|
||||
$col = $cell->getValue();
|
||||
if ($col instanceof RichText) {
|
||||
$col = $col->getPlainText();
|
||||
} else {
|
||||
$dataType = $cell->getDataType();
|
||||
if ($dataType == DataType::TYPE_NUMERIC || $dataType == DataType::TYPE_FORMULA) {
|
||||
# si c'est un format date, le forcer à une valeur standard
|
||||
$origFormatCode = $cell->getStyle()->getNumberFormat()->getFormatCode();
|
||||
if (strpbrk($origFormatCode, "ymdhs") !== false) {
|
||||
$formatCode = $origFormatCode;
|
||||
$formatCode = preg_replace('/y+/', "yyyy", $formatCode);
|
||||
$formatCode = preg_replace('/m+/', "mm", $formatCode);
|
||||
$formatCode = preg_replace('/d+/', "dd", $formatCode);
|
||||
$formatCode = preg_replace('/h+/', "hh", $formatCode);
|
||||
$formatCode = preg_replace('/s+/', "ss", $formatCode);
|
||||
$formatCode = preg_replace('/-+/', "/", $formatCode);
|
||||
$formatCode = preg_replace('/\\\\ /', " ", $formatCode);
|
||||
$formatCode = preg_replace('/;@$/', "", $formatCode);
|
||||
$formatCode = cl::get(self::FORMAT_MAPPINGS, $formatCode, $formatCode);
|
||||
if ($formatCode !== $origFormatCode) {
|
||||
$cell->getStyle()->getNumberFormat()->setFormatCode($formatCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
$col = $cell->getFormattedValue();
|
||||
$this->verifixCol($col);
|
||||
}
|
||||
} else {
|
||||
$col = null;
|
||||
}
|
||||
$row[] = $col;
|
||||
}
|
||||
if ($this->cook($row)) {
|
||||
yield $row;
|
||||
$this->idest++;
|
||||
}
|
||||
$this->isrc++;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,202 +0,0 @@
|
|||
<?php
|
||||
namespace nulib\ext\spreadsheet;
|
||||
|
||||
use nulib\file\csv\AbstractBuilder;
|
||||
use nulib\file\csv\TAbstractBuilder;
|
||||
use nulib\os\path;
|
||||
use nulib\php\func;
|
||||
use nulib\php\time\Date;
|
||||
use nulib\php\time\DateTime;
|
||||
use nulib\web\http;
|
||||
use OpenSpout\Common\Entity\Cell;
|
||||
use OpenSpout\Common\Entity\Style\Style;
|
||||
use OpenSpout\Common\Helper\CellTypeHelper;
|
||||
use OpenSpout\Writer\Common\Creator\WriterEntityFactory;
|
||||
use OpenSpout\Writer\WriterMultiSheetsAbstract;
|
||||
use OpenSpout\Writer\XLSX\Entity\SheetView;
|
||||
|
||||
class SpoutBuilder extends AbstractBuilder {
|
||||
use TAbstractBuilder;
|
||||
|
||||
const DATE_FORMAT = "mm/dd/yyyy";
|
||||
|
||||
const DATETIME_FORMAT = "mm/dd/yyyy hh:mm:ss";
|
||||
|
||||
/** @var bool faut-il choisir le type numérique pour une chaine numérique? */
|
||||
const TYPE_NUMERIC = true;
|
||||
|
||||
/** @var bool faut-il choisir le type date pour une chaine au bon format? */
|
||||
const TYPE_DATE = true;
|
||||
|
||||
/** @var string|int|null nom de la feuille dans laquelle écrire */
|
||||
const WSNAME = null;
|
||||
|
||||
function __construct(?string $output, ?array $params=null) {
|
||||
parent::__construct($output, $params);
|
||||
$ssType = $params["ss_type"] ?? null;
|
||||
if ($ssType === null) {
|
||||
switch (path::ext($this->output)) {
|
||||
case ".ods":
|
||||
$ssType = "ods";
|
||||
break;
|
||||
case ".xlsx":
|
||||
default:
|
||||
$ssType = "xlsx";
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch ($ssType) {
|
||||
case "ods":
|
||||
$ss = WriterEntityFactory::createODSWriter();
|
||||
break;
|
||||
case "xlsx":
|
||||
default:
|
||||
$ss = WriterEntityFactory::createXLSXWriter();
|
||||
break;
|
||||
}
|
||||
$ss->setDefaultColumnWidth(10.5);
|
||||
$ss->writeToStream($this->getResource());
|
||||
$this->ss = $ss;
|
||||
$this->typeNumeric = boolval($params["type_numeric"] ?? static::TYPE_NUMERIC);
|
||||
$this->typeDate = boolval($params["type_date"] ?? static::TYPE_DATE);
|
||||
$this->firstSheet = true;
|
||||
$this->setWsname($params["wsname"] ?? static::WSNAME);
|
||||
}
|
||||
|
||||
protected WriterMultiSheetsAbstract $ss;
|
||||
|
||||
protected bool $typeNumeric;
|
||||
|
||||
protected bool $typeDate;
|
||||
|
||||
const STYLE_ROW = 0, STYLE_HEADER = 1;
|
||||
|
||||
protected int $rowStyle;
|
||||
|
||||
protected bool $firstSheet;
|
||||
|
||||
/**
|
||||
* @param string|int|null $wsname
|
||||
*/
|
||||
function setWsname($wsname, ?array $params=null): self {
|
||||
$ss = $this->ss;
|
||||
$this->rowStyle = self::STYLE_ROW;
|
||||
if ($this->firstSheet) {
|
||||
$this->firstSheet = false;
|
||||
$ws = $ss->getCurrentSheet();
|
||||
} else {
|
||||
$ws = $ss->addNewSheetAndMakeItCurrent();
|
||||
$this->wroteHeaders = false;
|
||||
$this->built = false;
|
||||
}
|
||||
$wsname ??= $params["wsname"] ?? null;
|
||||
if ($wsname !== null) $ws->setName($wsname);
|
||||
$sheetView = (new SheetView())
|
||||
->setFreezeRow(2);
|
||||
$ws->setSheetView($sheetView);
|
||||
if ($params !== null) {
|
||||
if (array_key_exists("schema", $params)) {
|
||||
$this->schema = $params["schema"] ?? null;
|
||||
}
|
||||
if (array_key_exists("headers", $params)) {
|
||||
$this->headers = $params["headers"] ?? null;
|
||||
}
|
||||
if (array_key_exists("rows", $params)) {
|
||||
$rows = $params["rows"] ?? null;
|
||||
if (is_callable($rows)) $rows = $rows();
|
||||
$this->rows = $rows;
|
||||
}
|
||||
if (array_key_exists("cook_func", $params)) {
|
||||
$cookFunc = $params["cook_func"] ?? null;
|
||||
$cookCtx = $cookArgs = null;
|
||||
if ($cookFunc !== null) {
|
||||
func::ensure_func($cookFunc, $this, $cookArgs);
|
||||
$cookCtx = func::_prepare($cookFunc);
|
||||
}
|
||||
$this->cookCtx = $cookCtx;
|
||||
$this->cookArgs = $cookArgs;
|
||||
}
|
||||
if (array_key_exists("type_numeric", $params)) {
|
||||
$this->typeNumeric = boolval($params["type_numeric"] ?? static::TYPE_NUMERIC);
|
||||
}
|
||||
if (array_key_exists("type_date", $params)) {
|
||||
$this->typeDate = boolval($params["type_date"] ?? static::TYPE_DATE);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function isNumeric($value): bool {
|
||||
if ($this->typeNumeric && is_numeric($value)) return true;
|
||||
if (!is_string($value) && is_numeric($value)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function isDate(&$value, &$style): bool {
|
||||
if (CellTypeHelper::isDateTimeOrDateInterval($value)) {
|
||||
$style = (new Style())->setFormat(self::DATE_FORMAT);
|
||||
return true;
|
||||
}
|
||||
if (!is_string($value) || !$this->typeDate) return false;
|
||||
if (DateTime::isa_datetime($value, true)) {
|
||||
$value = new DateTime($value);
|
||||
$style = (new Style())->setFormat(self::DATETIME_FORMAT);
|
||||
return true;
|
||||
}
|
||||
if (DateTime::isa_date($value, true)) {
|
||||
$value = new Date($value);
|
||||
$style = (new Style())->setFormat(self::DATE_FORMAT);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function _write(array $row): void {
|
||||
$cells = [];
|
||||
$rowStyle = null;
|
||||
foreach ($row as $col) {
|
||||
$style = null;
|
||||
if ($col === null || $col === "") {
|
||||
$type = Cell::TYPE_EMPTY;
|
||||
} elseif ($this->isNumeric($col)) {
|
||||
$type = Cell::TYPE_NUMERIC;
|
||||
} elseif ($this->isDate($col, $style)) {
|
||||
$type = Cell::TYPE_DATE;
|
||||
} else {
|
||||
$type = Cell::TYPE_STRING;
|
||||
}
|
||||
$cell = WriterEntityFactory::createCell($col, $style);
|
||||
$cell->setType($type);
|
||||
$cells[] = $cell;
|
||||
}
|
||||
if ($this->rowStyle === self::STYLE_HEADER) {
|
||||
$rowStyle = (new Style())->setFontBold();
|
||||
}
|
||||
$this->ss->addRow(WriterEntityFactory::createRow($cells, $rowStyle));
|
||||
}
|
||||
|
||||
function writeHeaders(?array $headers=null): void {
|
||||
$this->rowStyle = self::STYLE_HEADER;
|
||||
parent::writeHeaders($headers);
|
||||
$this->rowStyle = self::STYLE_ROW;
|
||||
}
|
||||
|
||||
function _sendContentType(): void {
|
||||
switch (path::ext($this->output)) {
|
||||
case ".ods":
|
||||
$contentType = "application/vnd.oasis.opendocument.spreadsheet";
|
||||
break;
|
||||
case ".xlsx":
|
||||
default:
|
||||
$contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||
break;
|
||||
}
|
||||
http::content_type($contentType);
|
||||
}
|
||||
|
||||
protected function _checkOk(): bool {
|
||||
$this->ss->close();
|
||||
$this->rewind();
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
<?php
|
||||
namespace nulib\ext\spreadsheet;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\file\csv\AbstractReader;
|
||||
use OpenSpout\Reader\Common\Creator\ReaderEntityFactory;
|
||||
|
||||
class SpoutReader extends AbstractReader {
|
||||
/** @var string|int|null nom de la feuille depuis laquelle lire */
|
||||
const WSNAME = null;
|
||||
|
||||
function __construct($input, ?array $params=null) {
|
||||
parent::__construct($input, $params);
|
||||
$this->ssType = $params["ss_type"] ?? null;
|
||||
$this->allSheets = $params["all_sheets"] ?? true;
|
||||
$wsname = static::WSNAME;
|
||||
if ($params !== null && array_key_exists("wsname", $params)) {
|
||||
# spécifié par l'utilisateur: $allSheets = false
|
||||
$this->setWsname($params["wsname"]);
|
||||
} elseif ($wsname !== null) {
|
||||
# valeur non nulle de la classe: $allSheets = false
|
||||
$this->setWsname($wsname);
|
||||
} else {
|
||||
# pas de valeur définie dans la classe, laisser $allSheets à sa valeur
|
||||
# actuelle
|
||||
$this->wsname = null;
|
||||
}
|
||||
$this->includeWsnames = cl::withn($params["include_wsnames"] ?? null);
|
||||
$this->excludeWsnames = cl::withn($params["exclude_wsnames"] ?? null);
|
||||
}
|
||||
|
||||
protected ?string $ssType;
|
||||
|
||||
/** @var bool faut-il retourner les lignes de toutes les feuilles? */
|
||||
protected bool $allSheets;
|
||||
|
||||
function setAllSheets(bool $allSheets=true): self {
|
||||
$this->allSheets = $allSheets;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array|null si non null, liste de feuilles à inclure. n'est pris en
|
||||
* compte que si $allSheets===true
|
||||
*/
|
||||
protected ?array $includeWsnames;
|
||||
|
||||
/**
|
||||
* @var array|null si non null, liste de feuilles à exclure. n'est pris en
|
||||
* compte que si $allSheets===true
|
||||
*/
|
||||
protected ?array $excludeWsnames;
|
||||
|
||||
protected $wsname;
|
||||
|
||||
/**
|
||||
* @param string|int|null $wsname l'unique feuille à sélectionner
|
||||
*
|
||||
* NB: appeler cette méthode réinitialise $allSheets à false
|
||||
*/
|
||||
function setWsname($wsname): self {
|
||||
$this->wsname = $wsname;
|
||||
$this->allSheets = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
function getIterator() {
|
||||
switch ($this->ssType) {
|
||||
case "ods":
|
||||
$ss = ReaderEntityFactory::createODSReader();
|
||||
break;
|
||||
case "xlsx":
|
||||
$ss = ReaderEntityFactory::createXLSXReader();
|
||||
break;
|
||||
default:
|
||||
$ss = ReaderEntityFactory::createReaderFromFile($this->input);
|
||||
break;
|
||||
}
|
||||
$ss->open($this->input);
|
||||
try {
|
||||
$allSheets = $this->allSheets;
|
||||
$includeWsnames = $this->includeWsnames;
|
||||
$excludeWsnames = $this->excludeWsnames;
|
||||
$wsname = $this->wsname;
|
||||
$first = true;
|
||||
foreach ($ss->getSheetIterator() as $ws) {
|
||||
if ($allSheets) {
|
||||
$wsname = $ws->getName();
|
||||
$found = ($includeWsnames === null || in_array($wsname, $includeWsnames))
|
||||
&& ($excludeWsnames === null || !in_array($wsname, $excludeWsnames));
|
||||
} else {
|
||||
$found = $wsname === null || $wsname === $ws->getName();
|
||||
}
|
||||
if ($found) {
|
||||
if ($first) {
|
||||
$first = false;
|
||||
} else {
|
||||
yield null;
|
||||
# on garde le même schéma le cas échéant, mais supprimer headers
|
||||
# pour permettre son recalcul
|
||||
$this->headers = null;
|
||||
}
|
||||
$this->isrc = $this->idest = 0;
|
||||
foreach ($ws->getRowIterator() as $row) {
|
||||
$row = $row->toArray();
|
||||
foreach ($row as &$col) {
|
||||
$this->verifixCol($col);
|
||||
}; unset($col);
|
||||
if ($this->cook($row)) {
|
||||
yield $row;
|
||||
$this->idest++;
|
||||
}
|
||||
$this->isrc++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
$ss->close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
<?php
|
||||
namespace nulib\ext\spreadsheet;
|
||||
|
||||
/**
|
||||
* Class SsBuilder: construction d'une feuille de calcul, pour envoi à
|
||||
* l'utilisateur
|
||||
*/
|
||||
class SsBuilder extends SpoutBuilder {
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
<?php
|
||||
namespace nulib\ext\spreadsheet;
|
||||
|
||||
use nulib\file\csv\TAbstractReader;
|
||||
|
||||
class SsReader extends SpoutReader {
|
||||
use TAbstractReader;
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
<?php
|
||||
namespace nulib\ext\spreadsheet;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
|
||||
class ssutils {
|
||||
static function each_compute_max_coords(Spreadsheet $ss): array {
|
||||
$max_coords = [];
|
||||
foreach ($ss->getAllSheets() as $ws) {
|
||||
$max_coords[$ws->getTitle()] = wsutils::compute_max_coords($ws);
|
||||
}
|
||||
return $max_coords;
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
<?php
|
||||
namespace nulib\ext\spreadsheet;
|
||||
|
||||
use nulib\ValueException;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class wsutils {
|
||||
static function get_ws(?string $wsname, Spreadsheet $ss, bool $create=false): ?Worksheet {
|
||||
if ($wsname == null) {
|
||||
$ws = $ss->getActiveSheet();
|
||||
} elseif (is_numeric($wsname)) {
|
||||
$sheetCount = $ss->getSheetCount();
|
||||
if ($wsname < 1 || $wsname > $sheetCount) {
|
||||
throw ValueException::invalid_value($wsname, "sheet index");
|
||||
}
|
||||
$ws = $ss->getSheet($wsname - 1);
|
||||
} else {
|
||||
$ws = $ss->getSheetByName($wsname);
|
||||
if ($ws === null) {
|
||||
if ($create) $ws = $ss->createSheet()->setTitle($wsname);
|
||||
else throw ValueException::invalid_value($wsname, "sheet name");
|
||||
}
|
||||
}
|
||||
return $ws;
|
||||
}
|
||||
|
||||
static function get_highest_coords(Worksheet $ws): array {
|
||||
$highestColumnA = $ws->getHighestColumn();
|
||||
$highestCol = Coordinate::columnIndexFromString($highestColumnA);
|
||||
$highestRow = $ws->getHighestRow();
|
||||
return [$highestCol, $highestRow];
|
||||
}
|
||||
|
||||
/**
|
||||
* @var int nombre de colonnes/lignes au bout desquels on arrête de chercher
|
||||
* si on n'a trouvé que des cellules vides.
|
||||
*
|
||||
* c'est nécessaire à cause de certains fichiers provenant d'Excel que j'ai
|
||||
* reçus qui ont jusqu'à 10000 colonne vides et/ou 1048576 lignes vides. un
|
||||
* algorithme "bête" perd énormément de temps à chercher dans le vide, donnant
|
||||
* l'impression que le processus a planté.
|
||||
*/
|
||||
const MAX_EMPTY_THRESHOLD = 150;
|
||||
|
||||
static function compute_max_coords(Worksheet $ws): array {
|
||||
[$highestCol, $highestRow] = self::get_highest_coords($ws);
|
||||
|
||||
$maxCol = 1;
|
||||
$maxRow = 1;
|
||||
$maxEmptyRows = self::MAX_EMPTY_THRESHOLD;
|
||||
for ($row = 1; $row <= $highestRow; $row++) {
|
||||
$emptyRow = true;
|
||||
$maxEmptyCols = self::MAX_EMPTY_THRESHOLD;
|
||||
for ($col = 1; $col <= $highestCol; $col++) {
|
||||
$value = null;
|
||||
if ($ws->cellExistsByColumnAndRow($col, $row)) {
|
||||
$value = $ws->getCellByColumnAndRow($col, $row)->getValue();
|
||||
}
|
||||
if ($value === null) {
|
||||
$maxEmptyCols--;
|
||||
if ($maxEmptyCols == 0) break;
|
||||
} else {
|
||||
$maxEmptyCols = self::MAX_EMPTY_THRESHOLD;
|
||||
if ($row > $maxRow) $maxRow = $row;
|
||||
if ($col > $maxCol) $maxCol = $col;
|
||||
$emptyRow = false;
|
||||
}
|
||||
}
|
||||
if ($emptyRow) {
|
||||
$maxEmptyRows--;
|
||||
if ($maxEmptyRows == 0) break;
|
||||
} else {
|
||||
$maxEmptyRows = self::MAX_EMPTY_THRESHOLD;
|
||||
}
|
||||
}
|
||||
return [$maxCol, $maxRow];
|
||||
}
|
||||
}
|
|
@ -12,8 +12,13 @@ use nulib\file\TmpfileWriter;
|
|||
* Class file: outils pour gérer les fichiers
|
||||
*/
|
||||
class file {
|
||||
static function fix_dash($file) {
|
||||
if ($file === "-") $file = null;
|
||||
return $file;
|
||||
}
|
||||
|
||||
static function reader($input, ?callable $func=null): FileReader {
|
||||
$file = new FileReader($input);
|
||||
$file = new FileReader(self::fix_dash($input));
|
||||
if ($func !== null) {
|
||||
try {
|
||||
$func($file);
|
||||
|
@ -25,7 +30,7 @@ class file {
|
|||
}
|
||||
|
||||
static function writer($output, ?string $mode=null, ?callable $func=null): FileWriter {
|
||||
$file = new FileWriter($output, $mode);
|
||||
$file = new FileWriter(self::fix_dash($output), $mode);
|
||||
if ($func !== null) {
|
||||
try {
|
||||
$func($file);
|
||||
|
|
|
@ -5,7 +5,7 @@ use DateTimeInterface;
|
|||
use nulib\cl;
|
||||
use nulib\file\TempStream;
|
||||
use nulib\os\path;
|
||||
use nulib\php\func;
|
||||
use nulib\php\nur_func;
|
||||
use nulib\php\time\DateTime;
|
||||
use nulib\web\http;
|
||||
|
||||
|
@ -16,6 +16,12 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
|
|||
/** @var ?array liste des colonnes à écrire */
|
||||
const HEADERS = null;
|
||||
|
||||
/**
|
||||
* @var bool faut-il écrire les en-têtes, soit celles qui sont spécifiées,
|
||||
* soit celles qui sont calculées à partir des clés de la première ligne
|
||||
*/
|
||||
const USE_HEADERS = true;
|
||||
|
||||
/** @var ?string nom du fichier téléchargé */
|
||||
const OUTPUT = null;
|
||||
|
||||
|
@ -23,14 +29,15 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
|
|||
if ($output !== null) $params["output"] = $output;
|
||||
$this->schema = $params["schema"] ?? static::SCHEMA;
|
||||
$this->headers = $params["headers"] ?? static::HEADERS;
|
||||
$this->useHeaders = $params["use_headers"] ?? static::USE_HEADERS;
|
||||
$rows = $params["rows"] ?? null;
|
||||
if (is_callable($rows)) $rows = $rows();
|
||||
$this->rows = $rows;
|
||||
$cookFunc = $params["cook_func"] ?? null;
|
||||
$cookCtx = $cookArgs = null;
|
||||
if ($cookFunc !== null) {
|
||||
func::ensure_func($cookFunc, $this, $cookArgs);
|
||||
$cookCtx = func::_prepare($cookFunc);
|
||||
nur_func::ensure_func($cookFunc, $this, $cookArgs);
|
||||
$cookCtx = nur_func::_prepare($cookFunc);
|
||||
}
|
||||
$this->cookCtx = $cookCtx;
|
||||
$this->cookArgs = $cookArgs;
|
||||
|
@ -44,6 +51,8 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
|
|||
|
||||
protected ?array $headers;
|
||||
|
||||
protected bool $useHeaders;
|
||||
|
||||
protected ?iterable $rows;
|
||||
|
||||
protected ?string $output;
|
||||
|
@ -53,7 +62,7 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
|
|||
protected ?array $cookArgs;
|
||||
|
||||
protected function ensureHeaders(?array $row=null): void {
|
||||
if ($this->headers !== null) return;
|
||||
if ($this->headers !== null || !$this->useHeaders) return;
|
||||
if ($this->schema === null) $headers = null;
|
||||
else $headers = array_keys($this->schema);
|
||||
if ($headers === null && $row !== null) $headers = array_keys($row);
|
||||
|
@ -66,16 +75,18 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
|
|||
|
||||
function writeHeaders(?array $headers=null): void {
|
||||
if ($this->wroteHeaders) return;
|
||||
if ($headers !== null) $this->headers = $headers;
|
||||
else $this->ensureHeaders();
|
||||
if ($this->headers !== null) $this->_write($this->headers);
|
||||
if ($this->useHeaders) {
|
||||
if ($headers !== null) $this->headers = $headers;
|
||||
else $this->ensureHeaders();
|
||||
if ($this->headers !== null) $this->_write($this->headers);
|
||||
}
|
||||
$this->wroteHeaders = true;
|
||||
}
|
||||
|
||||
protected function cookRow(?array $row): ?array {
|
||||
if ($this->cookCtx !== null) {
|
||||
$args = cl::merge([$row], $this->cookArgs);
|
||||
$row = func::_call($this->cookCtx, $args);
|
||||
$row = nur_func::_call($this->cookCtx, $args);
|
||||
}
|
||||
if ($row !== null) {
|
||||
foreach ($row as &$value) {
|
||||
|
|
|
@ -5,11 +5,14 @@ use nulib\A;
|
|||
use nulib\php\time\Date;
|
||||
use nulib\php\time\DateTime;
|
||||
|
||||
abstract class AbstractReader implements IReader {
|
||||
abstract class AbstractReader implements IReader {
|
||||
const SCHEMA = null;
|
||||
|
||||
const HEADERS = null;
|
||||
|
||||
/** @var bool faut-il utiliser les en-têtes pour retourner des tableaux associatifs? */
|
||||
const USE_HEADERS = true;
|
||||
|
||||
/** @var ?string nom du fichier depuis lequel lire */
|
||||
const INPUT = null;
|
||||
|
||||
|
@ -17,18 +20,28 @@ abstract class AbstractReader implements IReader {
|
|||
const TRIM = true;
|
||||
|
||||
/** @var bool faut-il considérer les chaines vides comme null? */
|
||||
const PARSE_EMPTY_AS_NULL = true;
|
||||
const EMPTY_AS_NULL = true;
|
||||
|
||||
/**
|
||||
* @var bool ne pas essayer de deviner le format des données: les laisser au
|
||||
* format chaine.
|
||||
*/
|
||||
const PARSE_NOT = false;
|
||||
|
||||
/**
|
||||
* @var bool faut-il forcer le type numérique pour une chaine numérique?
|
||||
* si false, ne forcer le type numérique que si la chaine ne commence pas zéro
|
||||
* i.e "06" est une chaine, alors "63" est un nombre
|
||||
* si false, ne forcer le type numérique que si la chaine ne commence pas par
|
||||
* zéro i.e "06" est une chaine, alors "63" est un nombre
|
||||
*
|
||||
* ce paramètre est ignoré si PARSE_NOT == true
|
||||
*/
|
||||
const PARSE_NUMERIC = false;
|
||||
|
||||
/**
|
||||
* @var bool faut-il forcer le type {@link Date} ou {@link DateTime} pour une
|
||||
* chaine au bon format?
|
||||
*
|
||||
* ce paramètre est ignoré si PARSE_NOT == true
|
||||
*/
|
||||
const PARSE_DATE = true;
|
||||
|
||||
|
@ -37,9 +50,11 @@ abstract class AbstractReader implements IReader {
|
|||
#
|
||||
$this->schema = $params["schema"] ?? static::SCHEMA;
|
||||
$this->headers = $params["headers"] ?? static::HEADERS;
|
||||
$this->useHeaders = $params["use_headers"] ?? static::USE_HEADERS;
|
||||
$this->input = $params["input"] ?? static::INPUT;
|
||||
$this->trim = boolval($params["trim"] ?? static::TRIM);
|
||||
$this->parseEmptyAsNull = boolval($params["empty_as_null"] ?? static::PARSE_EMPTY_AS_NULL);
|
||||
$this->emptyAsNull = boolval($params["empty_as_null"] ?? static::EMPTY_AS_NULL);
|
||||
$this->parseNot = boolval($params["parse_not"] ?? static::PARSE_NOT);
|
||||
$this->parseNumeric = boolval($params["parse_numeric"] ?? static::PARSE_NUMERIC);
|
||||
$this->parseDate = boolval($params["parse_date"] ?? static::PARSE_DATE);
|
||||
}
|
||||
|
@ -48,11 +63,15 @@ abstract class AbstractReader implements IReader {
|
|||
|
||||
protected ?array $headers;
|
||||
|
||||
protected bool $useHeaders;
|
||||
|
||||
protected $input;
|
||||
|
||||
protected bool $trim;
|
||||
|
||||
protected bool $parseEmptyAsNull;
|
||||
protected bool $emptyAsNull;
|
||||
|
||||
protected bool $parseNot;
|
||||
|
||||
protected bool $parseNumeric;
|
||||
|
||||
|
@ -60,7 +79,8 @@ abstract class AbstractReader implements IReader {
|
|||
|
||||
protected int $isrc = 0, $idest = 0;
|
||||
|
||||
protected function cook(array &$row): bool {
|
||||
protected function cookRow(array &$row): bool {
|
||||
if (!$this->useHeaders) return true;
|
||||
if ($this->isrc == 0) {
|
||||
# ligne d'en-tête
|
||||
$headers = $this->headers;
|
||||
|
@ -81,11 +101,11 @@ abstract class AbstractReader implements IReader {
|
|||
if ($this->trim && is_string($col)) {
|
||||
$col = trim($col);
|
||||
}
|
||||
if ($this->parseEmptyAsNull && $col === "") {
|
||||
if ($this->emptyAsNull && $col === "") {
|
||||
# valeur vide --> null
|
||||
$col = null;
|
||||
}
|
||||
if (!is_string($col)) return;
|
||||
if (!is_string($col) || $this->parseNot) return;
|
||||
if ($this->parseDate) {
|
||||
if (DateTime::isa_datetime($col, true)) {
|
||||
# datetime
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
namespace nulib\file\csv;
|
||||
|
||||
use nulib\file;
|
||||
use nulib\file\FileReader;
|
||||
|
||||
class CsvReader extends AbstractReader {
|
||||
|
@ -17,7 +18,7 @@ class CsvReader extends AbstractReader {
|
|||
protected ?string $inputEncoding;
|
||||
|
||||
function getIterator() {
|
||||
$reader = new FileReader($this->input);
|
||||
$reader = new FileReader(file::fix_dash($this->input));
|
||||
$inputEncoding = $this->inputEncoding;
|
||||
if ($inputEncoding !== null) {
|
||||
$reader->appendFilter("convert.iconv.$inputEncoding.utf-8");
|
||||
|
@ -27,7 +28,7 @@ class CsvReader extends AbstractReader {
|
|||
foreach ($row as &$col) {
|
||||
$this->verifixCol($col);
|
||||
}; unset($col);
|
||||
if ($this->cook($row)) {
|
||||
if ($this->cookRow($row)) {
|
||||
yield $row;
|
||||
$this->idest++;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
namespace nulib\file\csv;
|
||||
|
||||
interface IBuilder {
|
||||
interface IBuilder extends \nulib\file\IReader {
|
||||
function writeHeaders(?array $headers=null): void;
|
||||
|
||||
function write(?array $row): void;
|
||||
|
|
|
@ -37,17 +37,15 @@ trait TAbstractBuilder {
|
|||
$output = $params["output"] ?? null;
|
||||
$ssType = null;
|
||||
if (is_string($output)) {
|
||||
switch (path::ext($output)) {
|
||||
case ".csv":
|
||||
$ext = path::ext($output);
|
||||
if ($output === "-" || $ext === ".csv") {
|
||||
$class = CsvBuilder::class;
|
||||
break;
|
||||
case ".ods":
|
||||
} elseif ($ext === ".ods") {
|
||||
$ssType = "ods";
|
||||
break;
|
||||
case ".xlsx":
|
||||
default:
|
||||
} elseif ($ext === ".xlsx") {
|
||||
$ssType = "xlsx";
|
||||
} else {
|
||||
$ssType = "xlsx";
|
||||
break;
|
||||
}
|
||||
}
|
||||
$params["ss_type"] = $ssType;
|
||||
|
|
|
@ -36,17 +36,15 @@ trait TAbstractReader {
|
|||
$input = $params["input"] ?? null;
|
||||
$ssType = null;
|
||||
if (is_string($input)) {
|
||||
switch (path::ext($input)) {
|
||||
case ".csv":
|
||||
$ext = path::ext($input);
|
||||
if ($input === "-" || $ext === ".csv") {
|
||||
$class = CsvReader::class;
|
||||
break;
|
||||
case ".ods":
|
||||
} elseif ($ext === ".ods") {
|
||||
$ssType = "ods";
|
||||
break;
|
||||
case ".xlsx":
|
||||
default:
|
||||
} elseif ($ext === ".xlsx") {
|
||||
$ssType = "xlsx";
|
||||
} else {
|
||||
$ssType = "xlsx";
|
||||
break;
|
||||
}
|
||||
}
|
||||
$params["ss_type"] = $ssType;
|
||||
|
|
|
@ -193,9 +193,9 @@ abstract class AbstractCmd implements ICmd {
|
|||
return $retcode == 0;
|
||||
}
|
||||
|
||||
function fork_exec(int &$retcode=null): bool {
|
||||
function fork_exec(?int &$retcode=null, bool $wait=true): bool {
|
||||
$cmd = $this->getCmd(null, true);
|
||||
sh::_fork_exec($cmd, $retcode);
|
||||
sh::_fork_exec($cmd, $retcode, $wait);
|
||||
return $retcode == 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<?php
|
||||
namespace nulib\os;
|
||||
|
||||
use nulib\app;
|
||||
use nulib\cl;
|
||||
use nulib\ExitError;
|
||||
use nulib\StateException;
|
||||
|
||||
class sh {
|
||||
|
@ -49,7 +51,7 @@ class sh {
|
|||
return implode(" ", $parts);
|
||||
}
|
||||
|
||||
private static final function add_redir(string &$cmd, ?string $redir, ?string $input, ?string $output): void {
|
||||
private static function add_redir(string &$cmd, ?string $redir, ?string $input, ?string $output): void {
|
||||
if ($redir !== null) {
|
||||
switch ($redir) {
|
||||
case "outonly":
|
||||
|
@ -134,27 +136,34 @@ class sh {
|
|||
return $retcode == 0;
|
||||
}
|
||||
|
||||
static function _waitpid(int $pid, ?int &$retcode=null): bool {
|
||||
pcntl_waitpid($pid, $status);
|
||||
if (pcntl_wifexited($status)) $retcode = pcntl_wexitstatus($status);
|
||||
elseif (pcntl_wifsignaled($status)) $retcode = -pcntl_wtermsig($status);
|
||||
else $retcode = app::EC_FORK_CHILD;
|
||||
return $retcode == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lancer la commande $cmd dans un processus fils via un shell et attendre la
|
||||
* fin de son exécution.
|
||||
*
|
||||
* $cmd doit déjà être formaté comme il convient
|
||||
*/
|
||||
static final function _fork_exec(string $cmd, int &$retcode=null): bool {
|
||||
static final function _fork_exec(string $cmd, ?int &$retcode=null, bool $wait=true): bool {
|
||||
$pid = pcntl_fork();
|
||||
if ($pid == -1) {
|
||||
// parent, impossible de forker
|
||||
throw new StateException("unable to fork");
|
||||
throw new ExitError(app::EC_FORK_PARENT, "unable to fork");
|
||||
} elseif ($pid) {
|
||||
// parent, fork ok
|
||||
pcntl_waitpid($pid, $status);
|
||||
if (pcntl_wifexited($status)) $retcode = pcntl_wexitstatus($status);
|
||||
else $retcode = 127;
|
||||
return $retcode == 0;
|
||||
if ($wait) return self::_waitpid($pid, $retcode);
|
||||
$retcode = null;
|
||||
return true;
|
||||
}
|
||||
// child, fork ok
|
||||
pcntl_exec("/bin/sh", ["-c", $cmd]);
|
||||
return false;
|
||||
throw StateException::unexpected_state();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -304,4 +313,51 @@ class sh {
|
|||
if (!is_link($f) || !is_link($g)) return false;
|
||||
return @readlink($f) === @readlink($g);
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
static function ls_all(string $dir, ?string $pattern=null, int $sorting_order=SCANDIR_SORT_ASCENDING): array {
|
||||
$all = scandir($dir, $sorting_order);
|
||||
if ($all === false) return [];
|
||||
return array_values(array_filter($all,
|
||||
function ($file) use ($pattern) {
|
||||
if ($file === "." || $file === "..") return false;
|
||||
return $pattern === null || fnmatch($pattern, $file);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
static function ls_dirs(string $dir, ?string $pattern=null, int $sorting_order=SCANDIR_SORT_ASCENDING): array {
|
||||
return array_values(array_filter(self::ls_all($dir, $pattern, $sorting_order),
|
||||
function ($file) use ($dir) {
|
||||
return path::is_dir(path::join($dir, $file));
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
static function ls_files(string $dir, ?string $pattern=null, int $sorting_order=SCANDIR_SORT_ASCENDING): array {
|
||||
return array_values(array_filter(self::ls_all($dir, $pattern, $sorting_order),
|
||||
function ($file) use ($dir) {
|
||||
return path::is_file(path::join($dir, $file));
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
static function ls_pall(string $dir, ?string $pattern=null, int $sorting_order=SCANDIR_SORT_ASCENDING): array {
|
||||
return array_map(function(string $name) use ($dir) {
|
||||
return path::join($dir, $name);
|
||||
}, self::ls_all($dir, $pattern, $sorting_order));
|
||||
}
|
||||
|
||||
static function ls_pdirs(string $dir, ?string $pattern=null, int $sorting_order=SCANDIR_SORT_ASCENDING): array {
|
||||
return array_map(function(string $name) use ($dir) {
|
||||
return path::join($dir, $name);
|
||||
}, self::ls_dirs($dir, $pattern, $sorting_order));
|
||||
}
|
||||
|
||||
static function ls_pfiles(string $dir, ?string $pattern=null, int $sorting_order=SCANDIR_SORT_ASCENDING): array {
|
||||
return array_map(function(string $name) use ($dir) {
|
||||
return path::join($dir, $name);
|
||||
}, self::ls_files($dir, $pattern, $sorting_order));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
namespace nulib\output;
|
||||
|
||||
use nulib\output\std\ProxyMessenger;
|
||||
use nulib\php\func;
|
||||
use nulib\php\nur_func;
|
||||
|
||||
/**
|
||||
* Class msg: inscrire un message dans les logs ET l'afficher sur la console
|
||||
* Class msg: inscrire un message dans les logs ET l'afficher à l'utilisateur
|
||||
*
|
||||
* Cette classe DOIT être initialisée avec {@link set_messenger()} ou
|
||||
* {@link set_messenger_class()} avant d'être utilisée.
|
||||
|
@ -40,8 +40,8 @@ class msg extends _messenger {
|
|||
if ($log instanceof IMessenger) log::set_messenger($log);
|
||||
elseif (is_string($log)) log::set_messenger_class($log);
|
||||
elseif (is_array($log)) {
|
||||
func::ensure_class($log, $args);
|
||||
$log = func::cons($log, $args);
|
||||
nur_func::ensure_class($log, $args);
|
||||
$log = nur_func::cons($log, $args);
|
||||
}
|
||||
log::set_messenger($log);
|
||||
$msgs[] = $log;
|
||||
|
@ -50,8 +50,8 @@ class msg extends _messenger {
|
|||
if ($console instanceof IMessenger) console::set_messenger($console);
|
||||
elseif (is_string($console)) console::set_messenger_class($console);
|
||||
elseif (is_array($console)) {
|
||||
func::ensure_class($console, $args);
|
||||
$console = func::cons($console, $args);
|
||||
nur_func::ensure_class($console, $args);
|
||||
$console = nur_func::cons($console, $args);
|
||||
}
|
||||
console::set_messenger($console);
|
||||
$msgs[] = $console;
|
||||
|
@ -60,8 +60,8 @@ class msg extends _messenger {
|
|||
if ($say instanceof IMessenger) say::set_messenger($say);
|
||||
elseif (is_string($say)) say::set_messenger_class($say);
|
||||
elseif (is_array($say)) {
|
||||
func::ensure_class($say, $args);
|
||||
$say = func::cons($say, $args);
|
||||
nur_func::ensure_class($say, $args);
|
||||
$say = nur_func::cons($say, $args);
|
||||
}
|
||||
say::set_messenger($say);
|
||||
$msgs[] = $say;
|
||||
|
|
|
@ -241,6 +241,7 @@ class StdMessenger implements _IMessenger {
|
|||
int $indentLevel, StdOutput $out): void {
|
||||
$prefixes = self::GENERIC_PREFIXES[$level][$type];
|
||||
if ($prefixes[0]) $out->print();
|
||||
$content = cl::with($content);
|
||||
if ($out->isColor()) {
|
||||
$before = $prefixes[2];
|
||||
$prefix = $prefixes[3];
|
||||
|
@ -249,7 +250,7 @@ class StdMessenger implements _IMessenger {
|
|||
$suffix2 = $suffix !== null? " $suffix": null;
|
||||
$after = $prefixes[5];
|
||||
|
||||
$lines = $out->getLines(false, $content);
|
||||
$lines = $out->getLines(false, ...$content);
|
||||
$maxlen = 0;
|
||||
foreach ($lines as &$content) {
|
||||
$line = $out->filterColors($content);
|
||||
|
@ -274,7 +275,7 @@ class StdMessenger implements _IMessenger {
|
|||
$prefix = $prefixes[1];
|
||||
if ($prefix !== null) $prefix .= " ";
|
||||
$prefix2 = str_repeat(" ", mb_strlen($prefix));
|
||||
$lines = $out->getLines(false, $content);
|
||||
$lines = $out->getLines(false, ...$content);
|
||||
foreach ($lines as $content) {
|
||||
if ($linePrefix !== null) $out->write($linePrefix);
|
||||
$out->iprint($indentLevel, $prefix, $content);
|
||||
|
@ -360,6 +361,7 @@ class StdMessenger implements _IMessenger {
|
|||
string $type, $content,
|
||||
int $indentLevel, StdOutput $out): void {
|
||||
$prefixes = self::GENERIC_PREFIXES[$level][$type];
|
||||
$content = cl::with($content);
|
||||
if ($out->isColor()) {
|
||||
$prefix = $prefixes[1];
|
||||
$prefix2 = null;
|
||||
|
@ -369,7 +371,7 @@ class StdMessenger implements _IMessenger {
|
|||
$prefix2 = str_repeat(" ", mb_strlen($prefix2));
|
||||
}
|
||||
$suffix = $prefixes[2];
|
||||
$lines = $out->getLines(false, $content);
|
||||
$lines = $out->getLines(false, ...$content);
|
||||
foreach ($lines as $content) {
|
||||
if ($linePrefix !== null) $out->write($linePrefix);
|
||||
$out->iprint($indentLevel, $prefix, $content, $suffix);
|
||||
|
@ -379,7 +381,7 @@ class StdMessenger implements _IMessenger {
|
|||
$prefix = $prefixes[0];
|
||||
if ($prefix !== null) $prefix .= " ";
|
||||
$prefix2 = str_repeat(" ", mb_strlen($prefix));
|
||||
$lines = $out->getLines(false, $content);
|
||||
$lines = $out->getLines(false, ...$content);
|
||||
foreach ($lines as $content) {
|
||||
if ($linePrefix !== null) $out->write($linePrefix);
|
||||
$out->iprint($indentLevel, $prefix, $content);
|
||||
|
|
|
@ -3,7 +3,7 @@ namespace nulib\php\content;
|
|||
|
||||
use Closure;
|
||||
use nulib\cl;
|
||||
use nulib\php\func;
|
||||
use nulib\php\nur_func;
|
||||
|
||||
/**
|
||||
* Class c: classe outil pour gérer du contenu
|
||||
|
@ -26,7 +26,7 @@ class c {
|
|||
}
|
||||
const nq = [self::class, "nq"];
|
||||
|
||||
private static final function add_static_content(array &$dest, iterable $values, $key, bool $seq): void {
|
||||
private static function add_static_content(array &$dest, iterable $values, $key, bool $seq): void {
|
||||
$sindex = 0;
|
||||
foreach ($values as $skey => $svalue) {
|
||||
if ($skey === $sindex) {
|
||||
|
@ -62,8 +62,8 @@ class c {
|
|||
# contenu dynamique: le contenu est la valeur de retour de la fonction
|
||||
# ce contenu est rajouté à la suite après avoir été quoté avec self::q()
|
||||
$func = $value;
|
||||
func::ensure_func($func, $object_or_class, $args);
|
||||
$values = self::q(func::call($func, ...$args));
|
||||
nur_func::ensure_func($func, $object_or_class, $args);
|
||||
$values = self::q(nur_func::call($func, ...$args));
|
||||
self::add_static_content($dest, $values, $key, $seq);
|
||||
continue;
|
||||
}
|
||||
|
@ -83,15 +83,15 @@ class c {
|
|||
$arg = self::resolve($arg, $object_or_class, false);
|
||||
if (!$array) $arg = $arg[0];
|
||||
}; unset($arg);
|
||||
if (func::is_static($func)) {
|
||||
func::ensure_func($func, $object_or_class, $args);
|
||||
$value = func::call($func, ...$args);
|
||||
} elseif (func::is_class($func)) {
|
||||
func::fix_class_args($func, $args);
|
||||
$value = func::cons($func, ...$args);
|
||||
if (nur_func::is_static($func)) {
|
||||
nur_func::ensure_func($func, $object_or_class, $args);
|
||||
$value = nur_func::call($func, ...$args);
|
||||
} elseif (nur_func::is_class($func)) {
|
||||
nur_func::fix_class_args($func, $args);
|
||||
$value = nur_func::cons($func, ...$args);
|
||||
} else {
|
||||
func::ensure_func($func, $object_or_class, $args);
|
||||
$value = func::call($func, ...$args);
|
||||
nur_func::ensure_func($func, $object_or_class, $args);
|
||||
$value = nur_func::call($func, ...$args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,14 +102,14 @@ class c {
|
|||
}
|
||||
const resolve = [self::class, "resolve"];
|
||||
|
||||
private static final function wend(?string $value): bool {
|
||||
private static function wend(?string $value): bool {
|
||||
return $value !== null && preg_match('/(\w|\w\.)$/', $value);
|
||||
}
|
||||
private static final function startw(?string $value): bool {
|
||||
private static function startw(?string $value): bool {
|
||||
return $value !== null && preg_match('/^\w/', $value);
|
||||
}
|
||||
|
||||
private static final function to_values($content, ?array &$values=null): void {
|
||||
private static function to_values($content, ?array &$values=null): void {
|
||||
$pvalue = cl::last($values);
|
||||
$wend = self::wend($pvalue);
|
||||
foreach ($content as $value) {
|
||||
|
|
1019
php/src/php/func.php
1019
php/src/php/func.php
File diff suppressed because it is too large
Load Diff
|
@ -44,7 +44,7 @@ class mprop {
|
|||
} catch (ReflectionException $e) {
|
||||
return oprop::get($object, $property, $default);
|
||||
}
|
||||
return func::call([$object, $m], $default);
|
||||
return nur_func::call([$object, $m], $default);
|
||||
}
|
||||
|
||||
/** spécifier la valeur d'une propriété */
|
||||
|
@ -53,14 +53,14 @@ class mprop {
|
|||
return self::_set($c, $object, $property, $value, $method);
|
||||
}
|
||||
|
||||
private static final function _set(ReflectionClass $c, object $object, string $property, $value, ?string $method) {
|
||||
private static function _set(ReflectionClass $c, object $object, string $property, $value, ?string $method) {
|
||||
if ($method === null) $method = self::get_setter_name($property);
|
||||
try {
|
||||
$m = $c->getMethod($method);
|
||||
} catch (ReflectionException $e) {
|
||||
return oprop::_set($c, $object, $property, $value);
|
||||
}
|
||||
func::call([$object, $m], $value);
|
||||
nur_func::call([$object, $m], $value);
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,453 @@
|
|||
<?php
|
||||
namespace nulib\php;
|
||||
|
||||
use Closure;
|
||||
use nulib\cl;
|
||||
use nulib\ref\php\ref_func;
|
||||
use nulib\ValueException;
|
||||
use ReflectionClass;
|
||||
use ReflectionFunction;
|
||||
use ReflectionMethod;
|
||||
|
||||
/**
|
||||
* Class func: outils pour appeler des fonctions et méthodes dynamiquement
|
||||
*/
|
||||
class nur_func {
|
||||
/**
|
||||
* tester si $func est une chaine de la forme "XXX::method" où XXX est une
|
||||
* chaine quelconque éventuellement vide, ou un tableau de la forme ["method"]
|
||||
* ou [anything, "method", ...]
|
||||
*
|
||||
* Avec la forme tableau, "method" ne doit pas contenir le caractère '\', pour
|
||||
* pouvoir utiliser conjointement {@link is_class()}
|
||||
*/
|
||||
static final function is_static($func, bool $allowClass=false): bool {
|
||||
if (is_string($func)) {
|
||||
$pos = strpos($func, "::");
|
||||
if ($pos === false) return false;
|
||||
return $pos + 2 < strlen($func);
|
||||
} elseif (is_array($func) && array_key_exists(0, $func)) {
|
||||
$count = count($func);
|
||||
if ($count == 1) {
|
||||
if (!is_string($func[0]) || strlen($func[0]) == 0) return false;
|
||||
if (strpos($func[0], "\\") !== false) return false;
|
||||
return true;
|
||||
} elseif ($count > 1) {
|
||||
if (!array_key_exists(1, $func)) return false;
|
||||
if (!is_string($func[1]) || strlen($func[1]) == 0) return false;
|
||||
if (strpos($func[1], "\\") !== false) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* si $func est une chaine de la forme "::method" alors la remplacer par la
|
||||
* chaine "$class::method"
|
||||
*
|
||||
* si $func est un tableau de la forme ["method"] ou [null, "method"], alors
|
||||
* le remplacer par [$class, "method"]
|
||||
*
|
||||
* on assume que {@link is_static()}($func) retourne true
|
||||
*
|
||||
* @return bool true si la correction a été faite
|
||||
*/
|
||||
static final function fix_static(&$func, $class): bool {
|
||||
if (is_object($class)) $class = get_class($class);
|
||||
|
||||
if (is_string($func) && substr($func, 0, 2) == "::") {
|
||||
$func = "$class$func";
|
||||
return true;
|
||||
} elseif (is_array($func) && array_key_exists(0, $func)) {
|
||||
$count = count($func);
|
||||
if ($count == 1) {
|
||||
$func = [$class, $func[0]];
|
||||
return true;
|
||||
} elseif ($count > 1 && $func[0] === null) {
|
||||
$func[0] = $class;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** tester si $method est une chaine de la forme "->method" */
|
||||
private static function isam($method): bool {
|
||||
return is_string($method)
|
||||
&& strlen($method) > 2
|
||||
&& substr($method, 0, 2) == "->";
|
||||
}
|
||||
|
||||
/**
|
||||
* tester si $func est une chaine de la forme "->method" ou un tableau de la
|
||||
* forme ["->method", ...] ou [anything, "->method", ...]
|
||||
*/
|
||||
static final function is_method($func): bool {
|
||||
if (is_string($func)) {
|
||||
return self::isam($func);
|
||||
} elseif (is_array($func) && array_key_exists(0, $func)) {
|
||||
if (self::isam($func[0])) {
|
||||
# ["->method", ...]
|
||||
return true;
|
||||
}
|
||||
if (array_key_exists(1, $func) && self::isam($func[1])) {
|
||||
# [anything, "->method", ...]
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* si $func est une chaine de la forme "->method" alors la remplacer par le
|
||||
* tableau [$object, "method"]
|
||||
*
|
||||
* si $func est un tableau de la forme ["->method"] ou [anything, "->method"],
|
||||
* alors le remplacer par [$object, "method"]
|
||||
*
|
||||
* @return bool true si la correction a été faite
|
||||
*/
|
||||
static final function fix_method(&$func, $object): bool {
|
||||
if (!is_object($object)) return false;
|
||||
|
||||
if (is_string($func)) {
|
||||
if (self::isam($func)) {
|
||||
$func = [$object, substr($func, 2)];
|
||||
return true;
|
||||
}
|
||||
} elseif (is_array($func) && array_key_exists(0, $func)) {
|
||||
if (self::isam($func[0])) $func = array_merge([null], $func);
|
||||
if (count($func) > 1 && array_key_exists(1, $func) && self::isam($func[1])) {
|
||||
$func[0] = $object;
|
||||
$func[1] = substr($func[1], 2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* si $func est un tableau de plus de 2 éléments, alors déplacer les éléments
|
||||
* supplémentaires au début de $args. par exemple:
|
||||
* ~~~
|
||||
* $func = ["class", "method", "arg1", "arg2"];
|
||||
* $args = ["arg3"];
|
||||
* func::fix_args($func, $args)
|
||||
* # $func === ["class", "method"]
|
||||
* # $args === ["arg1", "arg2", "arg3"]
|
||||
* ~~~
|
||||
*
|
||||
* @return bool true si la correction a été faite
|
||||
*/
|
||||
static final function fix_args(&$func, ?array &$args): bool {
|
||||
if ($args === null) $args = [];
|
||||
if (is_array($func) && count($func) > 2) {
|
||||
$prefix_args = array_slice($func, 2);
|
||||
$func = array_slice($func, 0, 2);
|
||||
$args = array_merge($prefix_args, $args);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* s'assurer que $func est un appel de méthode ou d'une méthode statique;
|
||||
* et renseigner le cas échéant les arguments. si $func ne fait pas mention
|
||||
* de la classe ou de l'objet, le renseigner avec $class_or_object.
|
||||
*
|
||||
* @return bool true si c'est une fonction valide. il ne reste plus qu'à
|
||||
* l'appeler avec {@link call()}
|
||||
*/
|
||||
static final function check_func(&$func, $class_or_object, &$args=null): bool {
|
||||
if ($func instanceof Closure) return true;
|
||||
if (self::is_method($func)) {
|
||||
# méthode
|
||||
self::fix_method($func, $class_or_object);
|
||||
self::fix_args($func, $args);
|
||||
return true;
|
||||
} elseif (self::is_static($func)) {
|
||||
# méthode statique
|
||||
self::fix_static($func, $class_or_object);
|
||||
self::fix_args($func, $args);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comme {@link check_func()} mais lance une exception si la fonction est
|
||||
* invalide
|
||||
*
|
||||
* @throws ValueException si $func n'est pas une fonction ou une méthode valide
|
||||
*/
|
||||
static final function ensure_func(&$func, $class_or_object, &$args=null): void {
|
||||
if (!self::check_func($func, $class_or_object, $args)) {
|
||||
throw ValueException::invalid_type($func, "callable");
|
||||
}
|
||||
}
|
||||
|
||||
static final function _prepare($func): array {
|
||||
$object = null;
|
||||
if (is_callable($func)) {
|
||||
if (is_array($func)) {
|
||||
$rf = new ReflectionMethod(...$func);
|
||||
$object = $func[0];
|
||||
if (is_string($object)) $object = null;
|
||||
} elseif ($func instanceof Closure) {
|
||||
$rf = new ReflectionFunction($func);
|
||||
} elseif (is_string($func) && strpos($func, "::") === false) {
|
||||
$rf = new ReflectionFunction($func);
|
||||
} else {
|
||||
$rf = new ReflectionMethod($func);
|
||||
}
|
||||
} elseif ($func instanceof ReflectionMethod) {
|
||||
$rf = $func;
|
||||
} elseif ($func instanceof ReflectionFunction) {
|
||||
$rf = $func;
|
||||
} elseif (is_array($func) && count($func) == 2 && isset($func[0]) && isset($func[1])
|
||||
&& ($func[1] instanceof ReflectionMethod || $func[1] instanceof ReflectionFunction)) {
|
||||
$object = $func[0];
|
||||
if (is_string($object)) $object = null;
|
||||
$rf = $func[1];
|
||||
} elseif (is_string($func) && strpos($func, "::") === false) {
|
||||
$rf = new ReflectionFunction($func);
|
||||
} else {
|
||||
throw ValueException::invalid_type($func, "callable");
|
||||
}
|
||||
$minArgs = $rf->getNumberOfRequiredParameters();
|
||||
$maxArgs = $rf->getNumberOfParameters();
|
||||
$variadic = $rf->isVariadic();
|
||||
return [$rf instanceof ReflectionMethod, $object, $rf, $minArgs, $maxArgs, $variadic];
|
||||
}
|
||||
|
||||
static final function _fill(array $context, array &$args): void {
|
||||
$minArgs = $context[3];
|
||||
$maxArgs = $context[4];
|
||||
$variadic = $context[5];
|
||||
if (!$variadic) $args = array_slice($args, 0, $maxArgs);
|
||||
while (count($args) < $minArgs) $args[] = null;
|
||||
}
|
||||
|
||||
static final function _call($context, array $args) {
|
||||
self::_fill($context, $args);
|
||||
$use_object = $context[0];
|
||||
$object = $context[1];
|
||||
$method = $context[2];
|
||||
if ($use_object) {
|
||||
if (count($args) === 0) return $method->invoke($object);
|
||||
else return $method->invokeArgs($object, $args);
|
||||
} else {
|
||||
if (count($args) === 0) return $method->invoke();
|
||||
else return $method->invokeArgs($args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appeler la fonction spécifiée avec les arguments spécifiés.
|
||||
* Adapter $args en fonction du nombre réel d'arguments de $func
|
||||
*
|
||||
* @param callable|ReflectionFunction|ReflectionMethod $func
|
||||
*/
|
||||
static final function call($func, ...$args) {
|
||||
return self::_call(self::_prepare($func), $args);
|
||||
}
|
||||
|
||||
/** remplacer $value par $func($value, ...$args) */
|
||||
static final function apply(&$value, $func, ...$args): void {
|
||||
if ($func !== null) {
|
||||
if ($args) $args = array_merge([$value], $args);
|
||||
else $args = [$value];
|
||||
$value = self::call($func, ...$args);
|
||||
}
|
||||
}
|
||||
|
||||
const MASK_PS = ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_STATIC;
|
||||
const MASK_P = ReflectionMethod::IS_PUBLIC;
|
||||
const METHOD_PS = ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_STATIC;
|
||||
const METHOD_P = ReflectionMethod::IS_PUBLIC;
|
||||
|
||||
private static function matches(string $name, array $includes, array $excludes): bool {
|
||||
if ($includes) {
|
||||
$matches = false;
|
||||
foreach ($includes as $include) {
|
||||
if (substr($include, 0, 1) == "/") {
|
||||
# expression régulière
|
||||
if (preg_match($include, $name)) {
|
||||
$matches = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
# tester la présence de la sous-chaine
|
||||
if (strpos($name, $include) !== false) {
|
||||
$matches = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$matches) return false;
|
||||
}
|
||||
foreach ($excludes as $exclude) {
|
||||
if (substr($exclude, 0, 1) == "/") {
|
||||
# expression régulière
|
||||
if (preg_match($exclude, $name)) return false;
|
||||
} else {
|
||||
# tester la présence de la sous-chaine
|
||||
if (strpos($name, $exclude) !== false) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @var Schema */
|
||||
private static $call_all_params_schema;
|
||||
|
||||
/**
|
||||
* retourner la liste des méthodes de $class_or_object qui correspondent au
|
||||
* filtre $options. le filtre doit respecter le schéme {@link CALL_ALL_PARAMS_SCHEMA}
|
||||
*/
|
||||
static function get_all($class_or_object, $params=null): array {
|
||||
Schema::nv($paramsv, $params, null
|
||||
, self::$call_all_params_schema, ref_func::CALL_ALL_PARAMS_SCHEMA);
|
||||
if (is_callable($class_or_object, true) && is_array($class_or_object)) {
|
||||
# callable sous forme de tableau
|
||||
$class_or_object = $class_or_object[0];
|
||||
}
|
||||
if (is_string($class_or_object)) {
|
||||
# lister les méthodes publiques statiques de la classe
|
||||
$mask = self::MASK_PS;
|
||||
$expected = self::METHOD_PS;
|
||||
$c = new ReflectionClass($class_or_object);
|
||||
} elseif (is_object($class_or_object)) {
|
||||
# lister les méthodes publiques de la classe
|
||||
$c = new ReflectionClass($class_or_object);
|
||||
$mask = $params["static_only"]? self::MASK_PS: self::MASK_P;
|
||||
$expected = $params["static_only"]? self::METHOD_PS: self::METHOD_P;
|
||||
} else {
|
||||
throw new ValueException("$class_or_object: vous devez spécifier une classe ou un objet");
|
||||
}
|
||||
$prefix = $params["prefix"]; $prefixlen = strlen($prefix);
|
||||
$args = $params["args"];
|
||||
$includes = $params["include"];
|
||||
$excludes = $params["exclude"];
|
||||
$methods = [];
|
||||
foreach ($c->getMethods() as $m) {
|
||||
if (($m->getModifiers() & $mask) != $expected) continue;
|
||||
$name = $m->getName();
|
||||
if (substr($name, 0, $prefixlen) != $prefix) continue;
|
||||
if (!self::matches($name, $includes, $excludes)) continue;
|
||||
$methods[] = cl::merge([$class_or_object, $name], $args);
|
||||
}
|
||||
return $methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appeler toutes les méthodes publiques de $object_or_class et retourner un
|
||||
* tableau [$method_name => $return_value] des valeurs de retour.
|
||||
*/
|
||||
static final function call_all($class_or_object, $params=null): array {
|
||||
$methods = self::get_all($class_or_object, $params);
|
||||
$values = [];
|
||||
foreach ($methods as $method) {
|
||||
self::fix_args($method, $args);
|
||||
$values[$method[1]] = self::call($method, ...$args);
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* tester si $func est une chaine de la forme "XXX" où XXX est une classe
|
||||
* valide, ou un tableau de la forme ["XXX", ...]
|
||||
*
|
||||
* NB: il est possible d'avoir {@link is_static()} et {@link is_class()}
|
||||
* vraies pour la même valeur. s'il faut supporter les deux cas, appeler
|
||||
* {@link is_static()} d'abord, mais dans ce cas, on ne supporte que les
|
||||
* classes qui sont dans un package
|
||||
*/
|
||||
static final function is_class($class): bool {
|
||||
if (is_string($class)) {
|
||||
return class_exists($class);
|
||||
} elseif (is_array($class) && array_key_exists(0, $class)) {
|
||||
return class_exists($class[0]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* en assumant que {@link is_class()} est vrai, si $class est un tableau de
|
||||
* plus de 1 éléments, alors déplacer les éléments supplémentaires au début de
|
||||
* $args. par exemple:
|
||||
* ~~~
|
||||
* $class = ["class", "arg1", "arg2"];
|
||||
* $args = ["arg3"];
|
||||
* func::fix_class_args($class, $args)
|
||||
* # $class === "class"
|
||||
* # $args === ["arg1", "arg2", "arg3"]
|
||||
* ~~~
|
||||
*
|
||||
* @return bool true si la correction a été faite
|
||||
*/
|
||||
static final function fix_class_args(&$class, ?array &$args): bool {
|
||||
if ($args === null) $args = [];
|
||||
if (is_array($class)) {
|
||||
if (count($class) > 1) {
|
||||
$prefix_args = array_slice($class, 1);
|
||||
$class = array_slice($class, 0, 1)[0];
|
||||
$args = array_merge($prefix_args, $args);
|
||||
} else {
|
||||
$class = $class[0];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* s'assurer que $class est une classe et renseigner le cas échéant les
|
||||
* arguments.
|
||||
*
|
||||
* @return bool true si c'est une classe valide. il ne reste plus qu'à
|
||||
* l'instancier avec {@link cons()}
|
||||
*/
|
||||
static final function check_class(&$class, &$args=null): bool {
|
||||
if (self::is_class($class)) {
|
||||
self::fix_class_args($class, $args);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comme {@link check_class()} mais lance une exception si la classe est
|
||||
* invalide
|
||||
*
|
||||
* @throws ValueException si $class n'est pas une classe valide
|
||||
*/
|
||||
static final function ensure_class(&$class, &$args=null): void {
|
||||
if (!self::check_class($class, $args)) {
|
||||
throw ValueException::invalid_type($class, "class");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instancier la classe avec les arguments spécifiés.
|
||||
* Adapter $args en fonction du nombre réel d'arguments du constructeur
|
||||
*/
|
||||
static final function cons(string $class, ...$args) {
|
||||
$c = new ReflectionClass($class);
|
||||
$rf = $c->getConstructor();
|
||||
if ($rf === null) {
|
||||
return $c->newInstance();
|
||||
} else {
|
||||
if (!$rf->isVariadic()) {
|
||||
$minArgs = $rf->getNumberOfRequiredParameters();
|
||||
$maxArgs = $rf->getNumberOfParameters();
|
||||
$args = array_slice($args, 0, $maxArgs);
|
||||
while (count($args) < $minArgs) {
|
||||
$args[] = null;
|
||||
}
|
||||
}
|
||||
return $c->newInstanceArgs($args);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
namespace nulib\ref\web;
|
||||
|
||||
class ref_mimetypes {
|
||||
const TXT = "text/plain";
|
||||
const CSV = "text/csv";
|
||||
|
||||
const BINARY = "application/octet-stream";
|
||||
const XLS = "application/vnd.ms-excel";
|
||||
const XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||
const ODS = "application/vnd.oasis.opendocument.spreadsheet";
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
namespace nulib\tools;
|
||||
|
||||
use nulib\ExitError;
|
||||
use nulib\ext\yaml;
|
||||
use nulib\os\path;
|
||||
use nulib\os\proc\Cmd;
|
||||
use nulib\os\sh;
|
||||
use nulib\output\msg;
|
||||
use nulib\app;
|
||||
use nulib\app\cli\Application;
|
||||
use nulib\app\RunFile;
|
||||
|
||||
class BgLauncherApp extends Application {
|
||||
const ACTION_INFOS = 0, ACTION_START = 1, ACTION_STOP = 2;
|
||||
const ARGS = [
|
||||
"purpose" => "lancer un script en tâche de fond",
|
||||
"usage" => "ApplicationClass args...",
|
||||
|
||||
"sections" => [
|
||||
parent::VERBOSITY_SECTION,
|
||||
],
|
||||
|
||||
["-i", "--infos", "name" => "action", "value" => self::ACTION_INFOS,
|
||||
"help" => "Afficher des informations sur la tâche",
|
||||
],
|
||||
["-s", "--start", "name" => "action", "value" => self::ACTION_START,
|
||||
"help" => "Démarrer la tâche",
|
||||
],
|
||||
["-k", "--stop", "name" => "action", "value" => self::ACTION_STOP,
|
||||
"help" => "Arrêter la tâche",
|
||||
],
|
||||
];
|
||||
|
||||
protected int $action = self::ACTION_START;
|
||||
|
||||
protected ?array $args = null;
|
||||
|
||||
static function show_infos(RunFile $runfile, ?int $level=null): void {
|
||||
msg::print($runfile->getDesc(), $level);
|
||||
msg::print(yaml::with(["data" => $runfile->read()]), ($level ?? 0) - 1);
|
||||
}
|
||||
|
||||
function main() {
|
||||
$args = $this->args;
|
||||
|
||||
$appClass = $args[0] ?? null;
|
||||
if ($appClass === null) {
|
||||
self::die("Vous devez spécifier la classe de l'application");
|
||||
}
|
||||
$appClass = $args[0] = str_replace("/", "\\", $appClass);
|
||||
if (!class_exists($appClass)) {
|
||||
self::die("$appClass: classe non trouvée");
|
||||
}
|
||||
|
||||
$useRunfile = constant("$appClass::USE_RUNFILE");
|
||||
if (!$useRunfile) {
|
||||
self::die("Cette application ne supporte le lancement en tâche de fond");
|
||||
}
|
||||
|
||||
$runfile = app::with($appClass)->getRunfile();
|
||||
switch ($this->action) {
|
||||
case self::ACTION_START:
|
||||
$argc = count($args);
|
||||
$appClass::_manage_runfile($argc, $args, $runfile);
|
||||
if ($runfile->warnIfLocked()) self::exit(app::EC_LOCKED);
|
||||
array_splice($args, 0, 0, [
|
||||
PHP_BINARY,
|
||||
path::abspath(NULIB_APP_app_launcher),
|
||||
]);
|
||||
app::params_putenv();
|
||||
self::_start($args, $runfile);
|
||||
break;
|
||||
case self::ACTION_STOP:
|
||||
self::_stop($runfile);
|
||||
self::show_infos($runfile, -1);
|
||||
break;
|
||||
case self::ACTION_INFOS:
|
||||
self::show_infos($runfile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static function _start(array $args, Runfile $runfile): void {
|
||||
$pid = pcntl_fork();
|
||||
if ($pid == -1) {
|
||||
# parent, impossible de forker
|
||||
throw new ExitError(app::EC_FORK_PARENT, "Unable to fork");
|
||||
} elseif (!$pid) {
|
||||
# child, fork ok
|
||||
$runfile->wfPrepare($pid);
|
||||
$outfile = $runfile->getOutfile() ?? "/tmp/NULIB_APP_app_console.out";
|
||||
$exitcode = app::EC_FORK_CHILD;
|
||||
try {
|
||||
# rediriger STDIN, STDOUT et STDERR
|
||||
fclose(fopen($outfile, "wb")); // vider le fichier
|
||||
fclose(STDIN); $in = fopen("/dev/null", "rb");
|
||||
fclose(STDOUT); $out = fopen($outfile, "ab");
|
||||
fclose(STDERR); $err = fopen($outfile, "ab");
|
||||
# puis lancer la commande
|
||||
$cmd = new Cmd($args);
|
||||
$cmd->addSource("/g/init.env");
|
||||
$cmd->addRedir("both", $outfile, true);
|
||||
$cmd->fork_exec($exitcode, false);
|
||||
sh::_waitpid(-$pid, $exitcode);
|
||||
} finally {
|
||||
$runfile->wfReaped($exitcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function _stop(Runfile $runfile): bool {
|
||||
$data = $runfile->read();
|
||||
$pid = $runfile->_getCid($data);
|
||||
msg::action("stop $pid");
|
||||
if ($runfile->wfKill($reason)) {
|
||||
msg::asuccess();
|
||||
return true;
|
||||
} else {
|
||||
msg::afailure($reason);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
namespace nulib\tools;
|
||||
|
||||
use nulib\output\msg;
|
||||
use nulib\php\time\DateTime;
|
||||
use nulib\text\words;
|
||||
use nulib\app;
|
||||
use nulib\app\cli\Application;
|
||||
|
||||
class SteamTrainApp extends Application {
|
||||
const PROJDIR = __DIR__.'/../..';
|
||||
const TITLE = "Train à vapeur";
|
||||
const USE_LOGFILE = true;
|
||||
const USE_RUNFILE = true;
|
||||
const USE_RUNLOCK = true;
|
||||
|
||||
const ARGS = [
|
||||
"purpose" => self::TITLE,
|
||||
"description" => <<<EOT
|
||||
Cette application peut être utilisée pour tester le lancement des tâches de fond
|
||||
EOT,
|
||||
|
||||
["-c", "--count", "args" => 1,
|
||||
"help" => "spécifier le nombre d'étapes",
|
||||
],
|
||||
["-f", "--force-enabled", "value" => true,
|
||||
"help" => "lancer la commande même si les tâches planifiées sont désactivées",
|
||||
],
|
||||
["-n", "--no-install-signal-handler", "value" => false,
|
||||
"help" => "ne pas installer le gestionnaire de signaux",
|
||||
],
|
||||
];
|
||||
|
||||
protected $count = 100;
|
||||
|
||||
protected bool $forceEnabled = false;
|
||||
|
||||
protected bool $installSignalHandler = true;
|
||||
|
||||
function main() {
|
||||
app::check_bgapplication_enabled($this->forceEnabled);
|
||||
if ($this->installSignalHandler) app::install_signal_handler();
|
||||
$count = intval($this->count);
|
||||
msg::info("Starting train for ".words::q($count, "step#s"));
|
||||
app::action("Running train...", $count);
|
||||
for ($i = 1; $i <= $count; $i++) {
|
||||
msg::print("Tchou-tchou! x $i");
|
||||
app::step();
|
||||
sleep(1);
|
||||
}
|
||||
msg::info("Stopping train at ".new DateTime());
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
<?php
|
||||
namespace nulib\app;
|
||||
|
||||
use nur\cli\Application;
|
||||
use nulib\output\msg;
|
||||
|
||||
class LongTaskApp extends Application {
|
||||
const APPCODE = "long-task";
|
||||
const USE_LOGFILE = true;
|
||||
const USE_RUNFILE = true;
|
||||
const USE_RUNLOCK = true;
|
||||
|
||||
function main() {
|
||||
$step = 100;
|
||||
while (--$step > 0) {
|
||||
msg::print("step $step");
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace nulib\app;
|
||||
|
||||
use nulib\tests\TestCase;
|
||||
use nulib\app\args;
|
||||
|
||||
class argsTest extends TestCase {
|
||||
function testFrom_array() {
|
||||
self::assertSame([], args::from_array(null));
|
||||
self::assertSame([], args::from_array([]));
|
||||
self::assertSame([], args::from_array([false]));
|
||||
self::assertSame(["x"], args::from_array(["x", false]));
|
||||
|
||||
self::assertSame(["--opt"], args::from_array(["--opt"]));
|
||||
self::assertSame(["--opt", "value"], args::from_array(["--opt", "value"]));
|
||||
|
||||
self::assertSame([], args::from_array(["opt" => false]));
|
||||
self::assertSame(["--opt"], args::from_array(["opt" => true]));
|
||||
self::assertSame(["--opt", "value"], args::from_array(["opt" => "value"]));
|
||||
self::assertSame(["--opt", "42"], args::from_array(["opt" => 42]));
|
||||
self::assertSame(["--opt", "1", "2", "3", "--"], args::from_array(["opt" => [1, 2, 3]]));
|
||||
|
||||
self::assertSame(["x", "1", "2", "3", "y"], args::from_array(["x", [1, 2, 3], "y"]));
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
<?php
|
||||
namespace nulib\app;
|
||||
|
||||
use nulib\tests\TestCase;
|
||||
|
||||
class launcherTest extends TestCase {
|
||||
function testVerifix_args() {
|
||||
self::assertSame([], launcher::verifix_args([]));
|
||||
self::assertSame(["a"], launcher::verifix_args(["a"]));
|
||||
self::assertSame(["a", "--b"], launcher::verifix_args(["a", "--b"]));
|
||||
self::assertSame([], launcher::verifix_args(["a" => false]));
|
||||
self::assertSame(["--a"], launcher::verifix_args(["a" => true]));
|
||||
self::assertSame(["--a", "value"], launcher::verifix_args(["a" => "value"]));
|
||||
self::assertSame(["--a", "52"], launcher::verifix_args(["a" => 52]));
|
||||
self::assertSame(["--aa-bb", "value"], launcher::verifix_args(["aaBb" => "value"]));
|
||||
self::assertSame(["--aa-bb", "value"], launcher::verifix_args(["aa-Bb" => "value"]));
|
||||
self::assertSame(["--aa-bb", "value"], launcher::verifix_args(["aa_Bb" => "value"]));
|
||||
self::assertSame(["---aa-bb", "value"], launcher::verifix_args(["_aa_Bb" => "value"]));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
<?php
|
||||
namespace nulib {
|
||||
use nulib\tests\TestCase;
|
||||
use nulib\impl\config;
|
||||
use nulib\impl\myapp;
|
||||
use nulib\impl\MyApplication1;
|
||||
use nulib\impl\MyApplication2;
|
||||
|
||||
class appTest extends TestCase {
|
||||
function testWith() {
|
||||
$projdir = config::get_projdir();
|
||||
$cwd = getcwd();
|
||||
|
||||
myapp::reset();
|
||||
$app1 = myapp::with(MyApplication1::class);
|
||||
self::assertSame([
|
||||
"projdir" => $projdir,
|
||||
"vendor" => [
|
||||
"bindir" => "$projdir/vendor/bin",
|
||||
"autoload" => "$projdir/vendor/autoload.php",
|
||||
],
|
||||
"appcode" => "nur-sery",
|
||||
"cwd" => $cwd,
|
||||
"datadir" => "$projdir/devel",
|
||||
"etcdir" => "$projdir/devel/etc",
|
||||
"vardir" => "$projdir/devel/var",
|
||||
"logdir" => "$projdir/devel/log",
|
||||
"profile" => "devel",
|
||||
"appgroup" => null,
|
||||
"name" => "my-application1",
|
||||
"title" => null,
|
||||
], $app1->getParams());
|
||||
|
||||
$app2 = myapp::with(MyApplication2::class, $app1);
|
||||
self::assertSame([
|
||||
"projdir" => $projdir,
|
||||
"vendor" => [
|
||||
"bindir" => "$projdir/vendor/bin",
|
||||
"autoload" => "$projdir/vendor/autoload.php",
|
||||
],
|
||||
"appcode" => "nur-sery",
|
||||
"cwd" => $cwd,
|
||||
"datadir" => "$projdir/devel",
|
||||
"etcdir" => "$projdir/devel/etc",
|
||||
"vardir" => "$projdir/devel/var",
|
||||
"logdir" => "$projdir/devel/log",
|
||||
"profile" => "devel",
|
||||
"appgroup" => null,
|
||||
"name" => "my-application2",
|
||||
"title" => null,
|
||||
], $app2->getParams());
|
||||
}
|
||||
|
||||
function testInit() {
|
||||
$projdir = config::get_projdir();
|
||||
$cwd = getcwd();
|
||||
|
||||
myapp::reset();
|
||||
myapp::init(MyApplication1::class);
|
||||
self::assertSame([
|
||||
"projdir" => $projdir,
|
||||
"vendor" => [
|
||||
"bindir" => "$projdir/vendor/bin",
|
||||
"autoload" => "$projdir/vendor/autoload.php",
|
||||
],
|
||||
"appcode" => "nur-sery",
|
||||
"cwd" => $cwd,
|
||||
"datadir" => "$projdir/devel",
|
||||
"etcdir" => "$projdir/devel/etc",
|
||||
"vardir" => "$projdir/devel/var",
|
||||
"logdir" => "$projdir/devel/log",
|
||||
"profile" => "devel",
|
||||
"appgroup" => null,
|
||||
"name" => "my-application1",
|
||||
"title" => null,
|
||||
], myapp::get()->getParams());
|
||||
|
||||
myapp::init(MyApplication2::class);
|
||||
self::assertSame([
|
||||
"projdir" => $projdir,
|
||||
"vendor" => [
|
||||
"bindir" => "$projdir/vendor/bin",
|
||||
"autoload" => "$projdir/vendor/autoload.php",
|
||||
],
|
||||
"appcode" => "nur-sery",
|
||||
"cwd" => $cwd,
|
||||
"datadir" => "$projdir/devel",
|
||||
"etcdir" => "$projdir/devel/etc",
|
||||
"vardir" => "$projdir/devel/var",
|
||||
"logdir" => "$projdir/devel/log",
|
||||
"profile" => "devel",
|
||||
"appgroup" => null,
|
||||
"name" => "my-application2",
|
||||
"title" => null,
|
||||
], myapp::get()->getParams());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace nulib\impl {
|
||||
|
||||
use nulib\app\cli\Application;
|
||||
use nulib\os\path;
|
||||
use nulib\app;
|
||||
|
||||
class config {
|
||||
const PROJDIR = __DIR__.'/..';
|
||||
|
||||
static function get_projdir(): string {
|
||||
return path::abspath(self::PROJDIR);
|
||||
}
|
||||
}
|
||||
|
||||
class myapp extends app {
|
||||
static function reset(): void {
|
||||
self::$app = null;
|
||||
}
|
||||
}
|
||||
|
||||
class MyApplication1 extends Application {
|
||||
const PROJDIR = config::PROJDIR;
|
||||
|
||||
function main() {
|
||||
}
|
||||
}
|
||||
class MyApplication2 extends Application {
|
||||
const PROJDIR = null;
|
||||
|
||||
function main() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
namespace nulib\php\access;
|
||||
|
||||
use nulib\tests\TestCase;
|
||||
use nulib\wip\php\access\KeyAccess;
|
||||
use stdClass;
|
||||
|
||||
class KeyAccessTest extends TestCase {
|
||||
function testAccess() {
|
||||
$default = new stdClass();
|
||||
$array = ["null" => null, "false" => false, "empty" => ""];
|
||||
|
||||
#
|
||||
$a = new KeyAccess($array, "inexistant");
|
||||
self::assertFalse($a->exists());
|
||||
self::assertFalse($a->available());
|
||||
self::assertSame($default, $a->get($default));
|
||||
|
||||
$a = new KeyAccess($array, "null");
|
||||
self::assertTrue($a->exists());
|
||||
self::assertTrue($a->available());
|
||||
self::assertSame(null, $a->get($default));
|
||||
|
||||
$a = new KeyAccess($array, "false");
|
||||
self::assertTrue($a->exists());
|
||||
self::assertFalse($a->available());
|
||||
self::assertSame($default, $a->get($default));
|
||||
|
||||
$a = new KeyAccess($array, "empty");
|
||||
self::assertTrue($a->exists());
|
||||
self::assertTrue($a->available());
|
||||
self::assertSame("", $a->get($default));
|
||||
|
||||
#
|
||||
$a = new KeyAccess($array, "null", ["allow_null" => false]);
|
||||
self::assertTrue($a->exists());
|
||||
self::assertFalse($a->available());
|
||||
self::assertSame($default, $a->get($default));
|
||||
|
||||
$a = new KeyAccess($array, "null", ["allow_null" => true]);
|
||||
self::assertTrue($a->exists());
|
||||
self::assertTrue($a->available());
|
||||
self::assertSame(null, $a->get($default));
|
||||
|
||||
#
|
||||
$a = new KeyAccess($array, "false", ["allow_false" => false]);
|
||||
self::assertTrue($a->exists());
|
||||
self::assertFalse($a->available());
|
||||
self::assertSame($default, $a->get($default));
|
||||
|
||||
$a = new KeyAccess($array, "false", ["allow_false" => true]);
|
||||
self::assertTrue($a->exists());
|
||||
self::assertTrue($a->available());
|
||||
self::assertSame(false, $a->get($default));
|
||||
|
||||
#
|
||||
$a = new KeyAccess($array, "empty", ["allow_empty" => false]);
|
||||
self::assertTrue($a->exists());
|
||||
self::assertFalse($a->available());
|
||||
self::assertSame($default, $a->get($default));
|
||||
|
||||
$a = new KeyAccess($array, "empty", ["allow_empty" => true]);
|
||||
self::assertTrue($a->exists());
|
||||
self::assertTrue($a->available());
|
||||
self::assertSame("", $a->get($default));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
namespace nulib\php\access;
|
||||
|
||||
use nulib\tests\TestCase;
|
||||
use nulib\wip\php\access\ValueAccess;
|
||||
use stdClass;
|
||||
|
||||
class ValueAccessTest extends TestCase {
|
||||
function testAccess() {
|
||||
$default = new stdClass();
|
||||
|
||||
#
|
||||
$i = null;
|
||||
$a = new ValueAccess($i);
|
||||
self::assertFalse($a->exists());
|
||||
self::assertFalse($a->available());
|
||||
self::assertSame($default, $a->get($default));
|
||||
|
||||
$i = false;
|
||||
$a = new ValueAccess($i);
|
||||
self::assertTrue($a->exists());
|
||||
self::assertTrue($a->available());
|
||||
self::assertSame(false, $a->get($default));
|
||||
|
||||
$i = "";
|
||||
$a = new ValueAccess($i);
|
||||
self::assertTrue($a->exists());
|
||||
self::assertTrue($a->available());
|
||||
self::assertSame("", $a->get($default));
|
||||
|
||||
#
|
||||
$i = null;
|
||||
$a = new ValueAccess($i, ["allow_null" => false]);
|
||||
self::assertFalse($a->exists());
|
||||
self::assertFalse($a->available());
|
||||
self::assertSame($default, $a->get($default));
|
||||
|
||||
$i = null;
|
||||
$a = new ValueAccess($i, ["allow_null" => true]);
|
||||
self::assertTrue($a->exists());
|
||||
self::assertTrue($a->available());
|
||||
self::assertSame(null, $a->get($default));
|
||||
|
||||
#
|
||||
$i = false;
|
||||
$a = new ValueAccess($i, ["allow_false" => false]);
|
||||
self::assertTrue($a->exists());
|
||||
self::assertFalse($a->available());
|
||||
self::assertSame($default, $a->get($default));
|
||||
|
||||
$i = false;
|
||||
$a = new ValueAccess($i, ["allow_false" => true]);
|
||||
self::assertTrue($a->exists());
|
||||
self::assertTrue($a->available());
|
||||
self::assertSame(false, $a->get($default));
|
||||
|
||||
#
|
||||
$i = "";
|
||||
$a = new ValueAccess($i, ["allow_empty" => false]);
|
||||
self::assertTrue($a->exists());
|
||||
self::assertFalse($a->available());
|
||||
self::assertSame($default, $a->get($default));
|
||||
|
||||
$i = "";
|
||||
$a = new ValueAccess($i, ["allow_empty" => true]);
|
||||
self::assertTrue($a->exists());
|
||||
self::assertTrue($a->available());
|
||||
self::assertSame("", $a->get($default));
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,292 @@
|
|||
<?php
|
||||
|
||||
namespace {
|
||||
function func36(): int { return 36; }
|
||||
|
||||
function func_m1($a): array { return [$a]; }
|
||||
function func_o1($b=9): array { return [$b]; }
|
||||
function func_v(...$c): array { return [...$c]; }
|
||||
function func_m1o1($a, $b=9): array { return [$a, $b]; }
|
||||
function func_m1v($a, ...$c): array { return [$a, ...$c]; }
|
||||
function func_m1o1v($a, $b=9, ...$c): array { return [$a, $b, ...$c]; }
|
||||
function func_o1v($b=9, ...$c): array { return [$b, ...$c]; }
|
||||
}
|
||||
|
||||
namespace nulib\php {
|
||||
|
||||
use nulib\tests\TestCase;
|
||||
|
||||
class nur_funcTest extends TestCase {
|
||||
function testIs_static() {
|
||||
self::assertFalse(nur_func::is_static(null));
|
||||
self::assertFalse(nur_func::is_static(""));
|
||||
self::assertFalse(nur_func::is_static("::"));
|
||||
self::assertFalse(nur_func::is_static("xxx::"));
|
||||
self::assertFalse(nur_func::is_static([]));
|
||||
self::assertFalse(nur_func::is_static([""]));
|
||||
self::assertFalse(nur_func::is_static([null, ""]));
|
||||
self::assertFalse(nur_func::is_static(["xxx", ""]));
|
||||
|
||||
self::assertTrue(nur_func::is_static("::xxx"));
|
||||
self::assertTrue(nur_func::is_static(["xxx"]));
|
||||
self::assertTrue(nur_func::is_static([null, "yyy"]));
|
||||
self::assertTrue(nur_func::is_static(["xxx", "yyy"]));
|
||||
self::assertTrue(nur_func::is_static([null, "yyy", "aaa"]));
|
||||
self::assertTrue(nur_func::is_static(["xxx", "yyy", "aaa"]));
|
||||
}
|
||||
|
||||
function testFix_static() {
|
||||
$class = "class";
|
||||
$func = "::xxx";
|
||||
nur_func::fix_static($func, $class);
|
||||
self::assertSame("class::xxx", $func);
|
||||
$func = ["xxx"];
|
||||
nur_func::fix_static($func, $class);
|
||||
self::assertSame(["class", "xxx"], $func);
|
||||
$func = [null, "yyy"];
|
||||
nur_func::fix_static($func, $class);
|
||||
self::assertSame(["class", "yyy"], $func);
|
||||
$func = ["xxx", "yyy"];
|
||||
nur_func::fix_static($func, $class);
|
||||
self::assertSame(["xxx", "yyy"], $func);
|
||||
$func = [null, "yyy", "aaa"];
|
||||
nur_func::fix_static($func, $class);
|
||||
self::assertSame(["class", "yyy", "aaa"], $func);
|
||||
$func = ["xxx", "yyy", "aaa"];
|
||||
nur_func::fix_static($func, $class);
|
||||
self::assertSame(["xxx", "yyy", "aaa"], $func);
|
||||
}
|
||||
|
||||
function testIs_method() {
|
||||
self::assertFalse(nur_func::is_method(null));
|
||||
self::assertFalse(nur_func::is_method(""));
|
||||
self::assertFalse(nur_func::is_method("->"));
|
||||
self::assertFalse(nur_func::is_method([]));
|
||||
self::assertFalse(nur_func::is_method([""]));
|
||||
self::assertFalse(nur_func::is_method([null, "->"]));
|
||||
self::assertFalse(nur_func::is_method(["xxx", "->"]));
|
||||
|
||||
self::assertTrue(nur_func::is_method("->xxx"));
|
||||
self::assertTrue(nur_func::is_method(["->xxx"]));
|
||||
self::assertTrue(nur_func::is_method([null, "->yyy"]));
|
||||
self::assertTrue(nur_func::is_method(["xxx", "->yyy"]));
|
||||
self::assertTrue(nur_func::is_method([null, "->yyy", "aaa"]));
|
||||
self::assertTrue(nur_func::is_method(["xxx", "->yyy", "aaa"]));
|
||||
}
|
||||
|
||||
function testFix_method() {
|
||||
$object = new \stdClass();
|
||||
$func= "->xxx";
|
||||
nur_func::fix_method($func, $object);
|
||||
self::assertSame([$object, "xxx"], $func);
|
||||
$func= ["->xxx"];
|
||||
nur_func::fix_method($func, $object);
|
||||
self::assertSame([$object, "xxx"], $func);
|
||||
$func= [null, "->yyy"];
|
||||
nur_func::fix_method($func, $object);
|
||||
self::assertSame([$object, "yyy"], $func);
|
||||
$func= ["xxx", "->yyy"];
|
||||
nur_func::fix_method($func, $object);
|
||||
self::assertSame([$object, "yyy"], $func);
|
||||
$func= [null, "->yyy", "aaa"];
|
||||
nur_func::fix_method($func, $object);
|
||||
self::assertSame([$object, "yyy", "aaa"], $func);
|
||||
$func= ["xxx", "->yyy", "aaa"];
|
||||
nur_func::fix_method($func, $object);
|
||||
self::assertSame([$object, "yyy", "aaa"], $func);
|
||||
}
|
||||
|
||||
function testCall() {
|
||||
self::assertSame(36, nur_func::call("func36"));
|
||||
self::assertSame(12, nur_func::call(TC::class."::method"));
|
||||
self::assertSame(12, nur_func::call([TC::class, "method"]));
|
||||
$closure = function() {
|
||||
return 21;
|
||||
};
|
||||
self::assertSame(21, nur_func::call($closure));
|
||||
}
|
||||
|
||||
function test_prepare_fill() {
|
||||
# vérifier que les arguments sont bien remplis, en fonction du fait qu'ils
|
||||
# soient obligatoires, facultatifs ou variadiques
|
||||
|
||||
# m1
|
||||
self::assertSame([null], nur_func::call("func_m1"));
|
||||
self::assertSame([null], nur_func::call("func_m1", null));
|
||||
self::assertSame([null], nur_func::call("func_m1", null, null));
|
||||
self::assertSame([null], nur_func::call("func_m1", null, null, null));
|
||||
self::assertSame([null], nur_func::call("func_m1", null, null, null, null));
|
||||
self::assertSame([1], nur_func::call("func_m1", 1));
|
||||
self::assertSame([1], nur_func::call("func_m1", 1, 2));
|
||||
self::assertSame([1], nur_func::call("func_m1", 1, 2, 3));
|
||||
self::assertSame([1], nur_func::call("func_m1", 1, 2, 3, 4));
|
||||
|
||||
# o1
|
||||
self::assertSame([9], nur_func::call("func_o1"));
|
||||
self::assertSame([null], nur_func::call("func_o1", null));
|
||||
self::assertSame([null], nur_func::call("func_o1", null, null));
|
||||
self::assertSame([null], nur_func::call("func_o1", null, null, null));
|
||||
self::assertSame([null], nur_func::call("func_o1", null, null, null, null));
|
||||
self::assertSame([1], nur_func::call("func_o1", 1));
|
||||
self::assertSame([1], nur_func::call("func_o1", 1, 2));
|
||||
self::assertSame([1], nur_func::call("func_o1", 1, 2, 3));
|
||||
self::assertSame([1], nur_func::call("func_o1", 1, 2, 3, 4));
|
||||
|
||||
# v
|
||||
self::assertSame([], nur_func::call("func_v"));
|
||||
self::assertSame([null], nur_func::call("func_v", null));
|
||||
self::assertSame([null, null], nur_func::call("func_v", null, null));
|
||||
self::assertSame([null, null, null], nur_func::call("func_v", null, null, null));
|
||||
self::assertSame([null, null, null, null], nur_func::call("func_v", null, null, null, null));
|
||||
self::assertSame([1], nur_func::call("func_v", 1));
|
||||
self::assertSame([1, 2], nur_func::call("func_v", 1, 2));
|
||||
self::assertSame([1, 2, 3], nur_func::call("func_v", 1, 2, 3));
|
||||
self::assertSame([1, 2, 3, 4], nur_func::call("func_v", 1, 2, 3, 4));
|
||||
|
||||
# m1o1
|
||||
self::assertSame([null, 9], nur_func::call("func_m1o1"));
|
||||
self::assertSame([null, 9], nur_func::call("func_m1o1", null));
|
||||
self::assertSame([null, null], nur_func::call("func_m1o1", null, null));
|
||||
self::assertSame([null, null], nur_func::call("func_m1o1", null, null, null));
|
||||
self::assertSame([null, null], nur_func::call("func_m1o1", null, null, null, null));
|
||||
self::assertSame([1, 9], nur_func::call("func_m1o1", 1));
|
||||
self::assertSame([1, 2], nur_func::call("func_m1o1", 1, 2));
|
||||
self::assertSame([1, 2], nur_func::call("func_m1o1", 1, 2, 3));
|
||||
self::assertSame([1, 2], nur_func::call("func_m1o1", 1, 2, 3, 4));
|
||||
|
||||
# m1v
|
||||
self::assertSame([null], nur_func::call("func_m1v"));
|
||||
self::assertSame([null], nur_func::call("func_m1v", null));
|
||||
self::assertSame([null, null], nur_func::call("func_m1v", null, null));
|
||||
self::assertSame([null, null, null], nur_func::call("func_m1v", null, null, null));
|
||||
self::assertSame([null, null, null, null], nur_func::call("func_m1v", null, null, null, null));
|
||||
self::assertSame([1], nur_func::call("func_m1v", 1));
|
||||
self::assertSame([1, 2], nur_func::call("func_m1v", 1, 2));
|
||||
self::assertSame([1, 2, 3], nur_func::call("func_m1v", 1, 2, 3));
|
||||
self::assertSame([1, 2, 3, 4], nur_func::call("func_m1v", 1, 2, 3, 4));
|
||||
|
||||
# m1o1v
|
||||
self::assertSame([null, 9], nur_func::call("func_m1o1v"));
|
||||
self::assertSame([null, 9], nur_func::call("func_m1o1v", null));
|
||||
self::assertSame([null, null], nur_func::call("func_m1o1v", null, null));
|
||||
self::assertSame([null, null, null], nur_func::call("func_m1o1v", null, null, null));
|
||||
self::assertSame([null, null, null, null], nur_func::call("func_m1o1v", null, null, null, null));
|
||||
self::assertSame([1, 9], nur_func::call("func_m1o1v", 1));
|
||||
self::assertSame([1, 2], nur_func::call("func_m1o1v", 1, 2));
|
||||
self::assertSame([1, 2, 3], nur_func::call("func_m1o1v", 1, 2, 3));
|
||||
self::assertSame([1, 2, 3, 4], nur_func::call("func_m1o1v", 1, 2, 3, 4));
|
||||
|
||||
# o1v
|
||||
self::assertSame([9], nur_func::call("func_o1v"));
|
||||
self::assertSame([null], nur_func::call("func_o1v", null));
|
||||
self::assertSame([null, null], nur_func::call("func_o1v", null, null));
|
||||
self::assertSame([null, null, null], nur_func::call("func_o1v", null, null, null));
|
||||
self::assertSame([null, null, null, null], nur_func::call("func_o1v", null, null, null, null));
|
||||
self::assertSame([1], nur_func::call("func_o1v", 1));
|
||||
self::assertSame([1, 2], nur_func::call("func_o1v", 1, 2));
|
||||
self::assertSame([1, 2, 3], nur_func::call("func_o1v", 1, 2, 3));
|
||||
self::assertSame([1, 2, 3, 4], nur_func::call("func_o1v", 1, 2, 3, 4));
|
||||
}
|
||||
|
||||
function testCall_all() {
|
||||
$c1 = new C1();
|
||||
$c2 = new C2();
|
||||
$c3 = new C3();
|
||||
|
||||
self::assertSameValues([11, 12], nur_func::call_all(C1::class));
|
||||
self::assertSameValues([11, 12, 21, 22], nur_func::call_all($c1));
|
||||
self::assertSameValues([13, 11, 12], nur_func::call_all(C2::class));
|
||||
self::assertSameValues([13, 23, 11, 12, 21, 22], nur_func::call_all($c2));
|
||||
self::assertSameValues([111, 13, 12], nur_func::call_all(C3::class));
|
||||
self::assertSameValues([111, 121, 13, 23, 12, 22], nur_func::call_all($c3));
|
||||
|
||||
$options = "conf";
|
||||
self::assertSameValues([11], nur_func::call_all(C1::class, $options));
|
||||
self::assertSameValues([11, 21], nur_func::call_all($c1, $options));
|
||||
self::assertSameValues([11], nur_func::call_all(C2::class, $options));
|
||||
self::assertSameValues([11, 21], nur_func::call_all($c2, $options));
|
||||
self::assertSameValues([111], nur_func::call_all(C3::class, $options));
|
||||
self::assertSameValues([111, 121], nur_func::call_all($c3, $options));
|
||||
|
||||
$options = ["prefix" => "conf"];
|
||||
self::assertSameValues([11], nur_func::call_all(C1::class, $options));
|
||||
self::assertSameValues([11, 21], nur_func::call_all($c1, $options));
|
||||
self::assertSameValues([11], nur_func::call_all(C2::class, $options));
|
||||
self::assertSameValues([11, 21], nur_func::call_all($c2, $options));
|
||||
self::assertSameValues([111], nur_func::call_all(C3::class, $options));
|
||||
self::assertSameValues([111, 121], nur_func::call_all($c3, $options));
|
||||
|
||||
self::assertSameValues([11, 12], nur_func::call_all($c1, ["include" => "x"]));
|
||||
self::assertSameValues([11, 21], nur_func::call_all($c1, ["include" => "y"]));
|
||||
self::assertSameValues([11, 12, 21], nur_func::call_all($c1, ["include" => ["x", "y"]]));
|
||||
|
||||
self::assertSameValues([21, 22], nur_func::call_all($c1, ["exclude" => "x"]));
|
||||
self::assertSameValues([12, 22], nur_func::call_all($c1, ["exclude" => "y"]));
|
||||
self::assertSameValues([22], nur_func::call_all($c1, ["exclude" => ["x", "y"]]));
|
||||
|
||||
self::assertSameValues([12], nur_func::call_all($c1, ["include" => "x", "exclude" => "y"]));
|
||||
}
|
||||
|
||||
function testCons() {
|
||||
$obj1 = nur_func::cons(WoCons::class, 1, 2, 3);
|
||||
self::assertInstanceOf(WoCons::class, $obj1);
|
||||
|
||||
$obj2 = nur_func::cons(WithEmptyCons::class, 1, 2, 3);
|
||||
self::assertInstanceOf(WithEmptyCons::class, $obj2);
|
||||
|
||||
$obj3 = nur_func::cons(WithCons::class, 1, 2, 3);
|
||||
self::assertInstanceOf(WithCons::class, $obj3);
|
||||
self::assertSame(1, $obj3->first);
|
||||
}
|
||||
}
|
||||
|
||||
class WoCons {
|
||||
}
|
||||
class WithEmptyCons {
|
||||
function __construct() {
|
||||
}
|
||||
}
|
||||
class WithCons {
|
||||
public $first;
|
||||
function __construct($first) {
|
||||
$this->first = $first;
|
||||
}
|
||||
}
|
||||
|
||||
class TC {
|
||||
static function method() {
|
||||
return 12;
|
||||
}
|
||||
}
|
||||
|
||||
class C1 {
|
||||
static function confps1_xy() {
|
||||
return 11;
|
||||
}
|
||||
static function ps2_x() {
|
||||
return 12;
|
||||
}
|
||||
function confp1_y() {
|
||||
return 21;
|
||||
}
|
||||
function p2() {
|
||||
return 22;
|
||||
}
|
||||
}
|
||||
class C2 extends C1 {
|
||||
static function ps3() {
|
||||
return 13;
|
||||
}
|
||||
function p3() {
|
||||
return 23;
|
||||
}
|
||||
}
|
||||
class C3 extends C2 {
|
||||
static function confps1_xy() {
|
||||
return 111;
|
||||
}
|
||||
function confp1_y() {
|
||||
return 121;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,8 @@
|
|||
namespace nulib\schema\_scalar;
|
||||
|
||||
use nulib\tests\TestCase;
|
||||
use nulib\schema\SchemaException;
|
||||
use nulib\wip\schema\_scalar\ScalarSchema;
|
||||
use nulib\wip\schema\SchemaException;
|
||||
|
||||
class ScalarSchemaTest extends TestCase {
|
||||
const NULL_SCHEMA = [
|
||||
|
|
|
@ -3,8 +3,8 @@ namespace nulib\schema\types;
|
|||
|
||||
use Exception;
|
||||
use nulib\tests\TestCase;
|
||||
use nulib\schema\_scalar\ScalarValue;
|
||||
use nulib\schema\Schema;
|
||||
use nulib\wip\schema\_scalar\ScalarValue;
|
||||
use nulib\wip\schema\Schema;
|
||||
|
||||
class strTest extends TestCase {
|
||||
function commonTests($destv, &$dest, callable $destvSetter): void {
|
||||
|
|
Loading…
Reference in New Issue