diff --git a/php/cli/pman/ComposerFile.php b/php/cli/pman/ComposerFile.php index 1e72de2..e40a0df 100644 --- a/php/cli/pman/ComposerFile.php +++ b/php/cli/pman/ComposerFile.php @@ -2,17 +2,16 @@ namespace cli\pman; use nulib\cl; +use nulib\exceptions; use nulib\ext\json; use nulib\file; use nulib\os\path; -use nulib\ValueException; class ComposerFile { function __construct(string $composerFile=".", bool $ensureExists=true) { if (is_dir($composerFile)) $composerFile = path::join($composerFile, 'composer.json'); if ($ensureExists && !file_exists($composerFile)) { - $message = path::ppath($composerFile).": fichier introuvable"; - throw new ValueException($message); + throw exceptions::invalid_value(path::ppath($composerFile), "ce fichier", "il est introuvable"); } $this->composerFile = $composerFile; $this->load(); diff --git a/php/cli/pman/ComposerPmanFile.php b/php/cli/pman/ComposerPmanFile.php index 4587e16..1ab44c7 100644 --- a/php/cli/pman/ComposerPmanFile.php +++ b/php/cli/pman/ComposerPmanFile.php @@ -2,10 +2,10 @@ namespace cli\pman; use nulib\A; +use nulib\exceptions; use nulib\ext\yaml; use nulib\os\path; use nulib\str; -use nulib\ValueException; class ComposerPmanFile { const NAMES = [".composer.pman", ".pman"]; @@ -29,8 +29,7 @@ class ComposerPmanFile { } } if ($ensureExists && !file_exists($configFile)) { - $message = path::ppath($configFile).": fichier introuvable"; - throw new ValueException($message); + throw exceptions::invalid_value(path::ppath($configFile), "ce fichier", "il est introuvable"); } $this->configFile = $configFile; $this->load(); @@ -66,9 +65,7 @@ class ComposerPmanFile { function getProfileConfig(string $profile, ?array $composerRequires=null, ?array $composerRequireDevs=null): array { $config = $this->data["composer"][$profile] ?? null; - if ($config === null) { - throw new ValueException("$profile: profil invalide"); - } + if ($config === null) throw exceptions::invalid_value($profile, "ce profil"); if ($composerRequires !== null) { $matchRequires = $this->data["composer"]["match_require"]; foreach ($composerRequires as $dep => $version) { diff --git a/php/src/AccessException.php b/php/src/AccessException.php index 6996667..629ec39 100644 --- a/php/src/AccessException.php +++ b/php/src/AccessException.php @@ -1,36 +1,38 @@ trace = self::extract_trace($exception->getTrace()); $previous = $exception->getPrevious(); if ($previous !== null) $this->previous = new static($previous); + if ($exception instanceof UserException) { + $this->userMessage = $exception->getUserMessage(); + $this->techMessage = $exception->getTechMessage(); + } else { + $this->userMessage = null; + $this->techMessage = null; + } } - /** @var string */ - protected $class; + protected string $class; function getClass(): string { return $this->class; } - /** @var string */ - protected $message; + protected string $message; function getMessage(): string { return $this->message; @@ -61,22 +66,19 @@ class ExceptionShadow { return $this->code; } - /** @var string */ - protected $file; + protected string $file; function getFile(): string { return $this->file; } - /** @var int */ - protected $line; + protected int $line; function getLine(): int { return $this->line; } - /** @var array */ - protected $trace; + protected array $trace; function getTrace(): array { return $this->trace; @@ -92,10 +94,21 @@ class ExceptionShadow { return implode("\n", $lines); } - /** @var ExceptionShadow */ - protected $previous; + protected ?ExceptionShadow $previous; function getPrevious(): ?ExceptionShadow { return $this->previous; } + + protected ?array $userMessage; + + function getUserMessage(): ?array { + return $this->userMessage; + } + + protected ?array $techMessage; + + function getTechMessage(): ?array { + return $this->techMessage; + } } diff --git a/php/src/ExitError.php b/php/src/ExitError.php index a14c3a8..de8501b 100644 --- a/php/src/ExitError.php +++ b/php/src/ExitError.php @@ -18,8 +18,7 @@ class ExitError extends Error { return $this->getCode() !== 0; } - /** @var ?string */ - protected $userMessage; + protected ?string $userMessage; function haveUserMessage(): bool { return $this->userMessage !== null; diff --git a/php/src/StateException.php b/php/src/StateException.php index 3eadf1d..a2f6bfe 100644 --- a/php/src/StateException.php +++ b/php/src/StateException.php @@ -12,12 +12,12 @@ class StateException extends LogicException { if ($method === null) $method = "this method"; $message = "$method is not implemented"; if ($prefix) $prefix = "$prefix: "; - return new static($prefix.$message); + return new static("$prefix$message"); } static final function unexpected_state(?string $suffix=null): self { $message = "unexpected state"; if ($suffix) $suffix = ": $suffix"; - return new static($message.$suffix); + return new static("$message$suffix"); } } diff --git a/php/src/UserException.php b/php/src/UserException.php index 1bef745..7578093 100644 --- a/php/src/UserException.php +++ b/php/src/UserException.php @@ -1,90 +1,35 @@ getUserMessage(); - else return null; + function __construct($userMessage, $code=0, ?Throwable $previous=null) { + $this->userMessage = $userMessage = c::resolve($userMessage); + parent::__construct(c::to_string($userMessage), $code, $previous); } - /** @param Throwable|ExceptionShadow $e */ - static final function get_user_summary($e): string { - $parts = []; - $first = true; - while ($e !== null) { - $message = self::get_user_message($e); - if (!$message) $message = "(no message)"; - if ($first) $first = false; - else $parts[] = "caused by "; - $parts[] = get_class($e) . ": " . $message; - $e = $e->getPrevious(); - } - return implode(", ", $parts); - } + protected ?array $userMessage; - /** @param Throwable|ExceptionShadow $e */ - static function get_message($e): ?string { - $message = $e->getMessage(); - if (!$message && $e instanceof self) $message = $e->getUserMessage(); - return $message; - } - - /** @param Throwable|ExceptionShadow $e */ - static final function get_summary($e): string { - $parts = []; - $first = true; - while ($e !== null) { - $message = self::get_message($e); - if (!$message) $message = "(no message)"; - if ($first) $first = false; - else $parts[] = "caused by "; - if ($e instanceof ExceptionShadow) $class = $e->getClass(); - else $class = get_class($e); - $parts[] = "$class: $message"; - $e = $e->getPrevious(); - } - return implode(", ", $parts); - } - - /** @param Throwable|ExceptionShadow $e */ - static final function get_traceback($e): string { - $tbs = []; - $previous = false; - while ($e !== null) { - if (!$previous) { - $efile = $e->getFile(); - $eline = $e->getLine(); - $tbs[] = "at $efile($eline)"; - } else { - $tbs[] = "~~ caused by: " . self::get_summary($e); - } - $tbs[] = $e->getTraceAsString(); - $e = $e->getPrevious(); - $previous = true; - #XXX il faudrait ne pas réinclure les lignes communes aux exceptions qui - # ont déjà été affichées - } - return implode("\n", $tbs); - } - - function __construct($userMessage, $techMessage=null, $code=0, ?Throwable $previous=null) { - $this->userMessage = $userMessage; - if ($techMessage === null) $techMessage = $userMessage; - parent::__construct($techMessage, $code, $previous); - } - - /** @var ?string */ - protected $userMessage; - - function getUserMessage(): ?string { + function getUserMessage(): ?array { return $this->userMessage; } + + protected ?array $techMessage = null; + + function getTechMessage(): ?array { + return $this->techMessage; + } + + function setTechMessage($techMessage): self { + $techMessage ??= c::resolve($techMessage); + $this->techMessage = $techMessage; + return $this; + } } diff --git a/php/src/ValueException.php b/php/src/ValueException.php index 12813d2..b321866 100644 --- a/php/src/ValueException.php +++ b/php/src/ValueException.php @@ -5,72 +5,4 @@ namespace nulib; * Class ValueException: indiquer qu'une valeur est invalide */ class ValueException extends UserException { - private static function value($value): string { - if (is_object($value)) { - return "<".get_class($value).">"; - } elseif (is_array($value)) { - $values = $value; - $parts = []; - $index = 0; - foreach ($values as $key => $value) { - if ($key === $index) { - $index++; - $parts[] = self::value($value); - } else { - $parts[] = "$key=>".self::value($value); - } - } - return "[" . implode(", ", $parts) . "]"; - } elseif (is_string($value)) { - return $value; - } else { - return var_export($value, true); - } - } - - private static function message($value, ?string $message, ?string $kind, ?string $prefix, ?string $suffix): string { - if ($kind === null) $kind = "value"; - if ($message === null) $message = "$kind$suffix"; - if ($value !== null) { - $value = self::value($value); - if ($prefix) $prefix = "$prefix: $value"; - else $prefix = $value; - } - if ($prefix) $prefix = "$prefix: "; - return $prefix.$message; - } - - static final function null(?string $kind=null, ?string $prefix=null, ?string $message=null): self { - return new static(self::message(null, $message, $kind, $prefix, " should not be null")); - } - - static final function check_null($value, ?string $kind=null, ?string $prefix=null, ?string $message=null) { - if ($value === null) throw static::null($kind, $prefix, $message); - return $value; - } - - static final function invalid_kind($value=null, ?string $kind=null, ?string $prefix=null, ?string $message=null): self { - return new static(self::message($value, $message, $kind, $prefix, " is invalid")); - } - - static final function invalid_key($value, ?string $prefix=null, ?string $message=null): self { - return self::invalid_kind($value, "key", $prefix, $message); - } - - static final function invalid_value($value, ?string $prefix=null, ?string $message=null): self { - return self::invalid_kind($value, "value", $prefix, $message); - } - - static final function invalid_type($value, string $expected_type): self { - return new static(self::message($value, null, "type", null, " is invalid, expected $expected_type")); - } - - static final function invalid_class($class, string $expected_class): self { - if (is_object($class)) $class = get_class($class); - return new static(self::message($class, null, "class", null, " is invalid, expected $expected_class")); - } - - static final function forbidden($value=null, ?string $kind=null, ?string $prefix=null, ?string $message=null): self { - return new static(self::message($value, $message, $kind, $prefix, " is forbidden")); - } } diff --git a/php/src/app/app.php b/php/src/app/app.php index 13aa994..dc01e45 100644 --- a/php/src/app/app.php +++ b/php/src/app/app.php @@ -5,13 +5,13 @@ use nulib\A; use nulib\app\cli\Application; use nulib\app\config\ProfileManager; use nulib\cl; +use nulib\exceptions; use nulib\ExitError; use nulib\os\path; use nulib\os\sh; use nulib\php\func; use nulib\ref\ref_profiles; use nulib\str; -use nulib\ValueException; class app { private static function isa_Application($app): bool { @@ -59,7 +59,7 @@ class app { } elseif (is_array($app)) { $params = $app; } else { - throw ValueException::invalid_type($app, Application::class); + throw exceptions::invalid_type($app, "app", Application::class); } return $params; } @@ -410,7 +410,7 @@ class app { function fencedJoin(string $basedir, ?string ...$paths): string { $path = path::reljoin($basedir, ...$paths); if (!path::is_within($path, $basedir)) { - throw ValueException::invalid_value($path, "path"); + throw exceptions::invalid_type($path, $kind, "path"); } return $path; } diff --git a/php/src/app/args/AbstractArgsParser.php b/php/src/app/args/AbstractArgsParser.php index 92df216..f1fc241 100644 --- a/php/src/app/args/AbstractArgsParser.php +++ b/php/src/app/args/AbstractArgsParser.php @@ -6,7 +6,8 @@ use stdClass; abstract class AbstractArgsParser { protected function notEnoughArgs(int $needed, ?string $arg=null): ArgsException { if ($arg !== null) $arg .= ": "; - return new ArgsException("${arg}nécessite $needed argument(s) supplémentaires"); + $reason = $arg._exceptions::missing_value_message($needed); + return _exceptions::missing_value(null, null, $reason); } protected function checkEnoughArgs(?string $option, int $count): void { @@ -15,16 +16,17 @@ abstract class AbstractArgsParser { protected function tooManyArgs(int $count, int $expected, ?string $arg=null): ArgsException { if ($arg !== null) $arg .= ": "; - return new ArgsException("${arg}trop d'arguments (attendu $expected, reçu $count)"); + $reason = $arg._exceptions::unexpected_value_message($count - $expected); + return _exceptions::unexpected_value(null, null, $reason); } protected function invalidArg(string $arg): ArgsException { - return new ArgsException("$arg: argument invalide"); + return _exceptions::invalid_value($arg); } protected function ambiguousArg(string $arg, array $candidates): ArgsException { $candidates = implode(", ", $candidates); - return new ArgsException("$arg: argument ambigû (les valeurs possibles sont $candidates)"); + return new ArgsException("$arg: cet argument est ambigû (les valeurs possibles sont $candidates)"); } /** diff --git a/php/src/app/args/Aodef.php b/php/src/app/args/Aodef.php index 4486eb8..fd0c726 100644 --- a/php/src/app/args/Aodef.php +++ b/php/src/app/args/Aodef.php @@ -147,11 +147,11 @@ class Aodef { protected function processExtends(Aolist $argdefs): void { $option = $this->extends; if ($option === null) { - throw ArgsException::missing("extends", "destination arg"); + throw _exceptions::null_value("extends", "il doit spécifier l'argument destination"); } $dest = $argdefs->get($option); if ($dest === null) { - throw ArgsException::invalid($option, "destination arg"); + throw _exceptions::invalid_value($option, "extends", "il doit spécifier un argument valide"); } if ($this->ensureArray !== null) $dest->ensureArray = $this->ensureArray; @@ -178,7 +178,7 @@ class Aodef { $args = $ms[2] ?? null; $option = "--$name"; } else { - throw ArgsException::invalid($option, "long option"); + throw _exceptions::invalid_value($option, "cette option longue"); } } elseif (substr($option, 0, 1) === "-") { $type = self::TYPE_SHORT; @@ -187,7 +187,7 @@ class Aodef { $args = $ms[2] ?? null; $option = "-$name"; } else { - throw ArgsException::invalid($option, "short option"); + throw _exceptions::invalid_value($option, " cette option courte"); } } else { $type = self::TYPE_COMMAND; @@ -196,7 +196,7 @@ class Aodef { $args = null; $option = "$name"; } else { - throw ArgsException::invalid($option, "command"); + throw _exceptions::invalid_value($option, "cette commande"); } } if ($args === ":") { @@ -347,7 +347,7 @@ class Aodef { $haveNull = true; break; } else { - throw ArgsException::invalid("$desc: $arg", "option arg"); + throw _exceptions::invalid_value("$desc: $arg", $kind, "ce n'est pas un argument valide"); } } @@ -366,7 +366,7 @@ class Aodef { $haveNull = true; break; } else { - throw ArgsException::invalid("$desc: $arg", "option arg"); + throw _exceptions::invalid_value("$desc: $arg", $kind, "ce n'est pas un argument valide"); } } if (!$haveOpt) $haveNull = true; @@ -519,7 +519,7 @@ class Aodef { case "--set-args": $this->actionSetArgs($dest, $value); break; case "--set-command": $this->actionSetCommand($dest, $value); break; case "--show-help": $parser->actionPrintHelp($arg); break; - default: throw ArgsException::invalid($this->action, "arg action"); + default: throw _exceptions::invalid_value($this->action, $kind, "action non supportée"); } } diff --git a/php/src/app/args/Aogroup.php b/php/src/app/args/Aogroup.php index b9858f3..06e2df2 100644 --- a/php/src/app/args/Aogroup.php +++ b/php/src/app/args/Aogroup.php @@ -10,7 +10,7 @@ class Aogroup extends Aolist { function __construct(array $defs, bool $setup=false) { $marker = A::pop($defs, 0); if ($marker !== "group") { - throw ArgsException::invalid(null, "group"); + throw _exceptions::missing_value(null, $kind, "ce n'est pas un groupe valide"); } # réordonner les clés numériques $defs = array_merge($defs); diff --git a/php/src/app/args/ArgsException.php b/php/src/app/args/ArgsException.php index db2cfff..fe814e8 100644 --- a/php/src/app/args/ArgsException.php +++ b/php/src/app/args/ArgsException.php @@ -1,20 +1,7 @@ subChannels[] = $channel; } else { - throw ValueException::invalid_type($channel, CapacitorChannel::class); + throw exceptions::invalid_type($channel, "channel", CapacitorChannel::class); } } } diff --git a/php/src/db/CapacitorStorage.php b/php/src/db/CapacitorStorage.php index 4c399c4..b812408 100644 --- a/php/src/db/CapacitorStorage.php +++ b/php/src/db/CapacitorStorage.php @@ -5,8 +5,8 @@ use nulib\A; use nulib\cl; use nulib\cv; use nulib\db\_private\_migration; +use nulib\exceptions; use nulib\php\func; -use nulib\ValueException; use Traversable; /** @@ -596,7 +596,7 @@ abstract class CapacitorStorage { * si $filter n'est pas un tableau, il est transformé en ["id_" => $filter] */ function _one(CapacitorChannel $channel, $filter, ?array $mergeQuery=null): ?array { - if ($filter === null) throw ValueException::null("filter"); + if ($filter === null) throw exceptions::null_value("filter"); $this->_create($channel); $this->verifixFilter($channel, $filter); $raw = $this->db()->one(cl::merge([ diff --git a/php/src/db/_private/_base.php b/php/src/db/_private/_base.php index 2ca42f9..c554174 100644 --- a/php/src/db/_private/_base.php +++ b/php/src/db/_private/_base.php @@ -1,14 +1,14 @@ "create", "type" => "ddl"]; @@ -28,7 +28,7 @@ abstract class _base extends _common { $sql = _generic::parse($sql, $bindings); $meta = ["isa" => "generic", "type" => null]; } else { - throw ValueException::invalid_kind($sql, "query"); + throw exceptions::invalid_type($sql, "cette requête sql"); } } else { if (!is_string($sql)) $sql = strval($sql); diff --git a/php/src/db/_private/_common.php b/php/src/db/_private/_common.php index 575a53b..69b93ab 100644 --- a/php/src/db/_private/_common.php +++ b/php/src/db/_private/_common.php @@ -2,8 +2,8 @@ namespace nulib\db\_private; use nulib\cl; +use nulib\exceptions; use nulib\str; -use nulib\ValueException; class _common { protected static function consume(string $pattern, string &$string, ?array &$ms=null): bool { @@ -249,7 +249,7 @@ class _common { protected static function check_eof(string $tmpsql, string $usersql): void { self::consume(';\s*', $tmpsql); if ($tmpsql) { - throw new ValueException("unexpected value at end: $usersql"); + throw exceptions::invalid_value($usersql, "cette requête sql"); } } } diff --git a/php/src/db/_private/_insert.php b/php/src/db/_private/_insert.php index eb54980..a4fe118 100644 --- a/php/src/db/_private/_insert.php +++ b/php/src/db/_private/_insert.php @@ -2,7 +2,7 @@ namespace nulib\db\_private; use nulib\cl; -use nulib\ValueException; +use nulib\exceptions; class _insert extends _common { const SCHEMA = [ @@ -44,7 +44,7 @@ class _insert extends _common { } elseif ($into !== null) { $sql[] = $into; } else { - throw new ValueException("expected table name: $usersql"); + throw exceptions::invalid_value($usersql, "cette requête sql", "il faut spécifier la table"); } ## cols & values diff --git a/php/src/db/_private/_select.php b/php/src/db/_private/_select.php index 0dc0f0b..4fdc3ff 100644 --- a/php/src/db/_private/_select.php +++ b/php/src/db/_private/_select.php @@ -2,8 +2,8 @@ namespace nulib\db\_private; use nulib\cl; +use nulib\exceptions; use nulib\str; -use nulib\ValueException; class _select extends _common { const SCHEMA = [ @@ -101,7 +101,7 @@ class _select extends _common { $sql[] = "from"; $sql[] = $from; } else { - throw new ValueException("expected table name: $usersql"); + throw exceptions::invalid_value($usersql, "cette requête sql", "il faut spécifier la table"); } ## where diff --git a/php/src/db/pdo/Pdo.php b/php/src/db/pdo/Pdo.php index ba92e99..a12be17 100644 --- a/php/src/db/pdo/Pdo.php +++ b/php/src/db/pdo/Pdo.php @@ -6,8 +6,8 @@ use nulib\db\_private\_config; use nulib\db\_private\Tvalues; use nulib\db\IDatabase; use nulib\db\ITransactor; +use nulib\exceptions; use nulib\php\func; -use nulib\ValueException; class Pdo implements IDatabase { use Tvalues; @@ -203,7 +203,7 @@ class Pdo implements IDatabase { $this->transactors[] = $transactor; $transactor->willUpdate(); } else { - throw ValueException::invalid_type($transactor, ITransactor::class); + throw exceptions::invalid_type($transactor, "transactor", ITransactor::class); } } return $this; diff --git a/php/src/db/pgsql/Pgsql.php b/php/src/db/pgsql/Pgsql.php index bda2423..72e2ef8 100644 --- a/php/src/db/pgsql/Pgsql.php +++ b/php/src/db/pgsql/Pgsql.php @@ -6,8 +6,8 @@ use nulib\db\_private\_config; use nulib\db\_private\Tvalues; use nulib\db\IDatabase; use nulib\db\ITransactor; +use nulib\exceptions; use nulib\php\func; -use nulib\ValueException; class Pgsql implements IDatabase { use Tvalues; @@ -253,7 +253,7 @@ class Pgsql implements IDatabase { $this->transactors[] = $transactor; $transactor->willUpdate(); } else { - throw ValueException::invalid_type($transactor, ITransactor::class); + throw exceptions::invalid_type($transactor, "transactor", ITransactor::class); } } return $this; diff --git a/php/src/db/sqlite/Sqlite.php b/php/src/db/sqlite/Sqlite.php index c82ca43..7876c35 100644 --- a/php/src/db/sqlite/Sqlite.php +++ b/php/src/db/sqlite/Sqlite.php @@ -7,8 +7,8 @@ use nulib\db\_private\_config; use nulib\db\_private\Tvalues; use nulib\db\IDatabase; use nulib\db\ITransactor; +use nulib\exceptions; use nulib\php\func; -use nulib\ValueException; use SQLite3; use SQLite3Result; use SQLite3Stmt; @@ -254,7 +254,7 @@ class Sqlite implements IDatabase { $this->transactors[] = $transactor; $transactor->willUpdate(); } else { - throw ValueException::invalid_type($transactor, ITransactor::class); + throw exceptions::invalid_type($transactor, "transactor", ITransactor::class); } } return $this; diff --git a/php/src/exceptions.php b/php/src/exceptions.php index afad896..8bfcb9f 100644 --- a/php/src/exceptions.php +++ b/php/src/exceptions.php @@ -1,12 +1,82 @@ getUserMessage(); + elseif ($e instanceof ExceptionShadow) $userMessage = $e->getUserMessage(); + else return null; + return c::to_string($userMessage); + } + + /** @param Throwable|ExceptionShadow $e */ + public static function get_tech_message($e): ?string { + if ($e instanceof UserException) $techMessage = $e->getTechMessage(); + elseif ($e instanceof ExceptionShadow) $techMessage = $e->getTechMessage(); + else return null; + return c::to_string($techMessage); + } + + /** @param Throwable|ExceptionShadow $e */ + public static function get_message($e): string { + if ($e instanceof UserException) $userMessage = $e->getUserMessage(); + elseif ($e instanceof ExceptionShadow) $userMessage = $e->getUserMessage(); + else return $e->getMessage(); + return c::to_string($userMessage); + } + + /** @param Throwable|ExceptionShadow $e */ + public static final function get_summary($e, bool $includePrevious = true): string { + $parts = []; + $first = true; + while ($e !== null) { + $message = self::get_message($e); + if (!$message) $message = "(no message)"; + $techMessage = self::get_tech_message($e); + if ($techMessage) $message .= " |$techMessage|"; + if ($first) $first = false; + else $parts[] = ", caused by "; + if ($e instanceof ExceptionShadow) $class = $e->getClass(); + else $class = get_class($e); + $parts[] = "$class: $message"; + $e = $includePrevious ? $e->getPrevious() : null; + } + return implode("", $parts); + } + + /** @param Throwable|ExceptionShadow $e */ + public static final function get_traceback($e): string { + $tbs = []; + $previous = false; + while ($e !== null) { + if (!$previous) { + $efile = $e->getFile(); + $eline = $e->getLine(); + $tbs[] = "at $efile($eline)"; + } else { + $tbs[] = "~~ caused by: " . self::get_summary($e, false); + } + $tbs[] = $e->getTraceAsString(); + $e = $e->getPrevious(); + $previous = true; + #XXX il faudrait ne pas réinclure les lignes communes aux exceptions qui + # ont déjà été affichées + } + return implode("\n", $tbs); + } + + ############################################################################# + + const EXCEPTION = ValueException::class; + const WORD = "la valeur#s"; protected static Word $word; @@ -15,23 +85,59 @@ class exceptions { return self::$word ??= new Word(static::WORD); } + static function value($value): string { + if (is_object($value)) { + return "<".get_class($value).">"; + } elseif (is_array($value)) { + $values = $value; + $parts = []; + $index = 0; + foreach ($values as $key => $value) { + if ($key === $index) { + $index++; + $parts[] = self::value($value); + } else { + $parts[] = "$key=>".self::value($value); + } + } + return "[".implode(", ", $parts)."]"; + } elseif (is_string($value)) { + return $value; + } else { + return var_export($value, true); + } + } + + static function generic($value, ?string $kind, ?string $cause, ?string $reason=null, ?Throwable $previous=null): UserException { + $msg = ""; + if ($value !== null) { + $msg .= self::value($value); + $msg .= ": "; + } + $kind ??= self::word()->_ce(); + $msg .= $kind; + $cause ??= "est invalide"; + if ($cause) $msg .= " $cause"; + if ($reason) $msg .= ": $reason"; + $code = $previous !== null? $previous->getCode(): 0; + $class = static::EXCEPTION; + return new $class($msg, $code, $previous); + } + /** * indiquer qu'une valeur est invalide pour une raison générique */ - static function invalid_value($value, ?string $reason=null): UserException { - $msg = var_export($value, true); - $msg .= self::word()->_ce(); - $msg .= " est invalide"; - if ($reason) $msg .= ": $reason"; - return new UserException($msg); + static function invalid_value($value, ?string $kind=null, ?string $reason=null, ?Throwable $previous=null): UserException { + return self::generic($value, $kind, null, $reason, $previous); } /** * spécialisation de {@link self::invalid_value()} qui permet d'indiquer les * types attendus */ - static function invalid_type($value, $expectedTypes=null): UserException { - $pronom = self::word()->pronom(); + static function invalid_type($value, ?string $kind=null, $expectedTypes=null, ?Throwable $previous=null): UserException { + if ($kind !== null) $pronom = "il"; + else $pronom = self::word()->pronom(); $expectedTypes = cl::withn($expectedTypes); if (!$expectedTypes) { $reason = null; @@ -41,11 +147,12 @@ class exceptions { $reason = "$pronom doit être d'un des types suivants: "; } $reason .= implode(", ", $expectedTypes); - return self::invalid_value($value, $reason); + return self::invalid_value($value, $kind, $reason, $previous); } - static function invalid_format($value, $expectedFormats=null): UserException { - $pronom = self::word()->pronom(); + static function invalid_format($value, ?string $kind=null, $expectedFormats=null, ?Throwable $previous=null): UserException { + if ($kind !== null) $pronom = "il"; + else $pronom = self::word()->pronom(); $expectedFormats = cl::withn($expectedFormats); if (!$expectedFormats) { $reason = null; @@ -55,51 +162,90 @@ class exceptions { $reason = "$pronom doit être dans l'un des formats suivants: "; } $reason .= implode(", ", $expectedFormats); - return self::invalid_value($value, $reason); + return self::invalid_value($value, $kind, $reason, $previous); } - static function out_of_range($value, ?int $min=null, ?int $max=null): UserException { - $pronom = self::word()->pronom(); + static function forbidden_value($value, ?string $kind=null, $allowedValues=null, ?Throwable $previous=null): UserException { + if ($kind !== null) $pronom = "il"; + else $pronom = self::word()->pronom(); + $allowedValues = cl::withn($allowedValues); + if (!$allowedValues) $reason = null; + else $reason = "$pronom doit faire partie de cette liste: "; + $reason .= implode(", ", $allowedValues); + return self::invalid_value($value, $kind, $reason, $previous); + } + + static function out_of_range($value, ?string $kind=null, ?int $min=null, ?int $max=null, ?Throwable $previous=null): UserException { + if ($kind !== null) { + $pronom = "il"; + $compris = "compris"; + $superieur = "supérieur"; + $inferieur = "inférieur"; + } else { + $word = self::word(); + $pronom = $word->pronom(); + $compris = $word->isFeminin()? "comprise": "compris"; + $superieur = $word->isFeminin()? "supérieure": "supérieur"; + $inferieur = $word->isFeminin()? "inférieure": "inférieur"; + } if ($min !== null && $max !== null) { - $reason = "$pronom doit être compris entre $min et $max"; + $reason = "$pronom doit être $compris entre $min et $max"; } else if ($min !== null) { - $reason = "$pronom doit être supérieur à $min"; + $reason = "$pronom doit être $superieur à $min"; } elseif ($max !== null) { - $reason = "$pronom doit être inférieur à $max"; + $reason = "$pronom doit être $inferieur à $max"; } else { $reason = null; } - return self::invalid_value($value, $reason); + return self::invalid_value($value, $kind, $reason, $previous); } - static function null_value(?string $reason=null): UserException { - $msg = self::word()->_ce(); - $msg .= " ne doit pas être nulle"; - if ($reason) $msg .= ": $reason"; - return new UserException($msg); + static function null_value(?string $kind=null, ?string $reason=null, ?Throwable $previous=null): UserException { + if ($kind !== null) $nul = "null"; + else $nul = self::word()->isFeminin()? "nulle": "nul"; + return self::generic(null, $kind, "ne doit pas être $nul", $reason, $previous); + } + + static function missing_value_message(?int $amount=null, ?string $kind=null): string { + $message = "il manque "; + if ($kind !== null) { + if ($amount !== null) $message = "$amount $kind"; + else $message = $kind; + } else { + if ($amount !== null) $message .= self::word()->q($amount); + else $message .= self::word()->_un(); + } + return $message; } /** * indiquer qu'une valeur est manquante */ - static function missing_value(?int $amout=null, ?string $reason=null): UserException { - $msg = "il manque "; - if ($amout !== null) { - $msg .= self::word()->q($amout); + static function missing_value(?int $amount=null, ?string $kind=null, ?string $reason=null, ?Throwable $previous=null): UserException { + $reason ??= self::missing_value_message($amount, $kind); + $class = static::EXCEPTION; + return new $class($reason, null, $previous); + } + + static function unexpected_value_message(?int $amount=null, ?string $kind=null): string { + if ($amount !== null) { + if ($kind !== null) $kind = "$amount $kind"; + else $kind = self::word()->q($amount); + $message = "il y a $kind en trop"; } else { - $msg .= self::word()->_le(); + if ($kind !== null) $kind = "de $kind"; + else $kind = self::word()->_de(2); + $message = "il y a trop $kind"; } - if ($reason) $msg .= ": $reason"; - return new UserException($msg); + return $message; } /** * indiquer qu'une valeur est en trop */ - static function unexpected_value(?string $reason=null): UserException { - $msg = "il y a trop "; - $msg .= self::word()->_de(2); - if ($reason) $msg .= ": $reason"; - return new UserException($msg); + static function unexpected_value(?int $amount=null, ?string $kind=null, ?string $reason=null, ?Throwable $previous=null): UserException { + $reason ??= self::unexpected_value_message($amount, $kind); + $class = static::EXCEPTION; + return new $class($reason, null, $previous); } } diff --git a/php/src/file/SharedFile.php b/php/src/file/SharedFile.php index c14001e..62fae81 100644 --- a/php/src/file/SharedFile.php +++ b/php/src/file/SharedFile.php @@ -1,6 +1,7 @@ fd = $fd; $this->close = $close; $this->throwOnError = $throwOnError ?? static::THROW_ON_ERROR; diff --git a/php/src/file/tab/TAbstractBuilder.php b/php/src/file/tab/TAbstractBuilder.php index f12e1e0..e4d50c3 100644 --- a/php/src/file/tab/TAbstractBuilder.php +++ b/php/src/file/tab/TAbstractBuilder.php @@ -2,6 +2,7 @@ namespace nulib\file\tab; use nulib\cl; +use nulib\exceptions; use nulib\file\csv\CsvBuilder; use nulib\file\web\Upload; use nulib\os\path; @@ -32,7 +33,7 @@ trait TAbstractBuilder { } elseif (is_array($builder)) { $params = cl::merge($builder, $params); } elseif ($builder !== null) { - throw ValueException::invalid_type($builder, self::class); + throw exceptions::invalid_type($builder, "builder", self::class); } $output = $params["output"] ?? null; diff --git a/php/src/file/tab/TAbstractReader.php b/php/src/file/tab/TAbstractReader.php index f6037c7..2bdb538 100644 --- a/php/src/file/tab/TAbstractReader.php +++ b/php/src/file/tab/TAbstractReader.php @@ -2,6 +2,7 @@ namespace nulib\file\tab; use nulib\cl; +use nulib\exceptions; use nulib\file\csv\CsvReader; use nulib\file\web\Upload; use nulib\os\path; @@ -31,7 +32,7 @@ trait TAbstractReader { } elseif (is_array($reader)) { $params = cl::merge($reader, $params); } elseif ($reader !== null) { - throw ValueException::invalid_type($reader, self::class); + throw exceptions::invalid_type($reader, "reader", self::class); } $input = $params["input"] ?? null; diff --git a/php/src/mail/MailTemplate.php b/php/src/mail/MailTemplate.php index ee1739d..d007879 100644 --- a/php/src/mail/MailTemplate.php +++ b/php/src/mail/MailTemplate.php @@ -19,8 +19,8 @@ class MailTemplate { $texprs = $mail["exprs"] ?? []; $this->el = new ExpressionLanguage(); - ValueException::check_null($this->subject = $tsubject, "subject"); - ValueException::check_null($this->body = $tbody, "body"); + $this->subject = cv::not_null($tsubject, "subject"); + $this->body = cv::not_null($tbody, "body"); $exprs = []; # Commencer par extraire les expressions de la forme {name} if (preg_match_all('/\{([a-zA-Z_][a-zA-Z0-9_.-]*)}/', $this->body, $mss, PREG_SET_ORDER)) { diff --git a/php/src/mail/mailer.php b/php/src/mail/mailer.php index 3f92202..8f695d9 100644 --- a/php/src/mail/mailer.php +++ b/php/src/mail/mailer.php @@ -4,6 +4,7 @@ namespace nulib\mail; use nulib\app\config; use nulib\cl; use nulib\cv; +use nulib\exceptions; use nulib\output\msg; use nulib\str; use nulib\ValueException; @@ -90,7 +91,7 @@ class mailer { $host = $params["host"] ?? null; $port = $params["port"] ?? 25; if ($host === null) { - throw new ValueException("mail host is required"); + throw exceptions::null_value("host"); } msg::debug("new PHPMailer using SMTP to $host:$port"); $mailer->isSMTP(); @@ -106,7 +107,7 @@ class mailer { $mailer->isSendmail(); break; default: - throw ValueException::invalid_value($backend, "mailer backend"); + throw exceptions::forbidden_value($backend, "backend", ["smtp", "phpmail", "sendmail"]); } # debug $debug = $params["debug"] ?? SMTP::DEBUG_OFF; @@ -114,7 +115,7 @@ class mailer { if ($debug < SMTP::DEBUG_OFF) $debug = SMTP::DEBUG_OFF; elseif ($debug > SMTP::DEBUG_LOWLEVEL) $debug = SMTP::DEBUG_LOWLEVEL; } elseif (!self::is_bool($debug)) { - throw ValueException::invalid_value($debug, "debug mode"); + throw exceptions::invalid_type($debug, "debug", ["int", "bool"]); } $mailer->SMTPDebug = $debug; # auth, username, password @@ -144,7 +145,10 @@ class mailer { $mailer->SMTPSecure = $secure; break; default: - throw ValueException::invalid_value($secure, "encryption mode"); + throw exceptions::forbidden_value($secure, "secure", [ + PHPMailer::ENCRYPTION_SMTPS, + PHPMailer::ENCRYPTION_STARTTLS, + ]); } } @@ -186,7 +190,7 @@ class mailer { $tos = str::join(",", $tos); msg::debug("Sending to $tos"); if (!$mailer->send()) { - throw new MailerException("Une erreur s'est produite pendant l'envoi du mail", $mailer->ErrorInfo); + throw new MailerException("erreur d'envoi du mail", $mailer->ErrorInfo); } } diff --git a/php/src/output/_messenger.php b/php/src/output/_messenger.php index 1226c24..883b460 100644 --- a/php/src/output/_messenger.php +++ b/php/src/output/_messenger.php @@ -2,6 +2,7 @@ namespace nulib\output; use nulib\cl; +use nulib\exceptions; use nulib\str; use nulib\ValueException; @@ -15,7 +16,7 @@ abstract class _messenger { static function set_messenger_class(string $msg_class, ?array $params=null) { if (!is_subclass_of($msg_class, IMessenger::class)) { - throw ValueException::invalid_class($msg_class, IMessenger::class); + throw exceptions::invalid_type($msg_class, $kind, IMessenger::class); } static::set_messenger(new $msg_class($params)); } diff --git a/php/src/output/console.php b/php/src/output/console.php index a5ca3a9..9eb6b52 100644 --- a/php/src/output/console.php +++ b/php/src/output/console.php @@ -2,6 +2,7 @@ namespace nulib\output; use nulib\app\app; +use nulib\exceptions; use nulib\output\std\ProxyMessenger; use nulib\ValueException; @@ -63,7 +64,7 @@ class console extends _messenger { ]); break; default: - throw ValueException::invalid_value($verbosity, "verbosity"); + throw exceptions::forbidden_value($verbosity, $kind, ["silent", "quiet", "normal", "verbose", "debug"]); } } diff --git a/php/src/output/std/StdMessenger.php b/php/src/output/std/StdMessenger.php index c163aeb..4d80891 100644 --- a/php/src/output/std/StdMessenger.php +++ b/php/src/output/std/StdMessenger.php @@ -4,6 +4,7 @@ namespace nulib\output\std; use Exception; use nulib\A; use nulib\cl; +use nulib\exceptions; use nulib\ExceptionShadow; use nulib\output\IMessenger; use nulib\UserException; @@ -236,9 +237,11 @@ class StdMessenger implements _IMessenger { return $indentLevel; } - protected function _printTitle(?string $linePrefix, int $level, - string $type, $content, - int $indentLevel, StdOutput $out): void { + protected function _printTitle( + ?string $linePrefix, int $level, + string $type, $content, + int $indentLevel, StdOutput $out + ): void { $prefixes = self::GENERIC_PREFIXES[$level][$type]; if ($prefixes[0]) $out->print(); $content = cl::with($content); @@ -284,10 +287,12 @@ class StdMessenger implements _IMessenger { } } - protected function _printAction(?string $linePrefix, int $level, - bool $printContent, $content, - bool $printResult, ?bool $rsuccess, $rcontent, - int $indentLevel, StdOutput $out): void { + protected function _printAction( + ?string $linePrefix, int $level, + bool $printContent, $content, + bool $printResult, ?bool $rsuccess, $rcontent, + int $indentLevel, StdOutput $out + ): void { $color = $out->isColor(); if ($rsuccess === true) $type = "success"; elseif ($rsuccess === false) $type = "failure"; @@ -357,9 +362,11 @@ class StdMessenger implements _IMessenger { } } - protected function _printGeneric(?string $linePrefix, int $level, - string $type, $content, - int $indentLevel, StdOutput $out): void { + protected function _printGeneric( + ?string $linePrefix, int $level, + string $type, $content, + int $indentLevel, StdOutput $out + ): void { $prefixes = self::GENERIC_PREFIXES[$level][$type]; $content = cl::with($content); if ($out->isColor()) { @@ -390,7 +397,11 @@ class StdMessenger implements _IMessenger { } } - protected function _printGenericOrException(?int $level, string $type, $content, int $indentLevel, StdOutput $out): void { + protected function _printGenericOrException( + ?int $level, + string $type, $content, + int $indentLevel, StdOutput $out + ): void { $linePrefix = $this->getLinePrefix(); # si $content contient des exceptions, les afficher avec un level moindre $exceptions = null; @@ -421,27 +432,18 @@ class StdMessenger implements _IMessenger { $level1 = $this->decrLevel($level); $showTraceback = $this->checkLevel($level1); foreach ($exceptions as $exception) { - # tout d'abord userMessage - if ($exception instanceof UserException) { - $userMessage = UserException::get_user_message($exception); - $userMessage ??= "Une erreur technique s'est produite"; - $showSummary = true; - } else { - $userMessage = UserException::get_summary($exception); - $showSummary = false; - } - if ($userMessage !== null && $showContent) { + # tout d'abord message + $message = exceptions::get_message($exception); + if ($showContent) { if ($printActions) { $this->printActions(); $printActions = false; } - $this->_printGeneric($linePrefix, $level, $type, $userMessage, $indentLevel, $out); + $this->_printGeneric($linePrefix, $level, $type, $message, $indentLevel, $out); } # puis summary et traceback if ($showTraceback) { if ($printActions) { $this->printActions(); $printActions = false; } - if ($showSummary) { - $summary = UserException::get_summary($exception); - $this->_printGeneric($linePrefix, $level1, $type, $summary, $indentLevel, $out); - } - $traceback = UserException::get_traceback($exception); + $summary = exceptions::get_summary($exception, false); + $this->_printGeneric($linePrefix, $level1, $type, $summary, $indentLevel, $out); + $traceback = exceptions::get_traceback($exception); $this->_printGeneric($linePrefix, $level1, $type, $traceback, $indentLevel, $out); } } diff --git a/php/src/php/func.php b/php/src/php/func.php index 1c0123c..976a761 100644 --- a/php/src/php/func.php +++ b/php/src/php/func.php @@ -6,8 +6,8 @@ use Exception; use nulib\A; use nulib\cl; use nulib\cv; +use nulib\exceptions; use nulib\StateException; -use nulib\ValueException; use ReflectionClass; use ReflectionFunction; use ReflectionMethod; @@ -446,11 +446,7 @@ class func { const TYPE_STATIC = self::TYPE_METHOD | self::FLAG_STATIC; protected static function not_a_callable($func, ?string $reason) { - if ($reason === null) { - $msg = var_export($func, true); - $reason = "$msg: not a callable"; - } - return new ValueException($reason); + throw exceptions::invalid_type($func, null, "callable"); } private static function _with($func, ?array $args=null, bool $strict=true, ?string &$reason=null): ?self { @@ -604,7 +600,7 @@ class func { $mask = $staticOnly? self::MASK_PS: self::MASK_P; $expected = $staticOnly? self::METHOD_PS: self::METHOD_P; } else { - throw new ValueException("$class_or_object: vous devez spécifier une classe ou un objet"); + throw exceptions::invalid_type($class_or_object, null, ["class", "object"]); } $methods = []; foreach ($c->getMethods() as $m) { @@ -777,7 +773,7 @@ class func { if (is_object($object) && !($this->flags & self::FLAG_STATIC)) { if (is_object($c)) $c = get_class($c); if (is_string($c) && !($object instanceof $c)) { - throw ValueException::invalid_type($object, $c); + throw exceptions::invalid_type($object, "object", $c); } $this->object = $object; $this->bound = true; diff --git a/php/src/str.php b/php/src/str.php index 689c9d0..c330b37 100644 --- a/php/src/str.php +++ b/php/src/str.php @@ -438,7 +438,7 @@ class str { } elseif (preg_match(self::CAMEL_PATTERN2, $camel, $ms, PREG_OFFSET_CAPTURE)) { # préfixe en minuscule } else { - throw ValueException::invalid_kind($camel, "camel string"); + throw exceptions::invalid_type($camel, $kind, "camel string"); } $parts[] = strtolower($ms[1][0]); $index = intval($ms[1][1]) + strlen($ms[1][0]); diff --git a/php/src/text/Word.php b/php/src/text/Word.php index fb5851c..b25cfe3 100644 --- a/php/src/text/Word.php +++ b/php/src/text/Word.php @@ -119,6 +119,14 @@ class Word { $this->w = $spec; } + function isMasculin(): bool { + return !$this->fem; + } + + function isFeminin(): bool { + return $this->fem; + } + /** * retourner le mot sans article * diff --git a/php/src/web/curl/CurlException.php b/php/src/web/curl/CurlException.php index 53fda92..52e87a0 100644 --- a/php/src/web/curl/CurlException.php +++ b/php/src/web/curl/CurlException.php @@ -5,9 +5,8 @@ use nulib\UserException; use Throwable; class CurlException extends UserException { - function __construct($ch, ?string $message=null, $code=0, ?Throwable $previous=null) { - if ($message === null) $message = "(unknown error)"; - $userMessage = $message; + function __construct($ch, $userMessage=null, $code=0, ?Throwable $previous=null) { + $userMessage ??= "erreur curl inconnue"; $techMessage = null; if ($ch !== null) { $parts = []; @@ -17,6 +16,7 @@ class CurlException extends UserException { if ($error != "") $parts[] = "error: $error"; if ($parts) $techMessage = implode(", ", $parts); } - parent::__construct($userMessage, $techMessage, $code, $previous); + parent::__construct($userMessage, $code, $previous); + $this->setTechMessage($techMessage); } } diff --git a/php/src/web/curl/curl.php b/php/src/web/curl/curl.php index 34f0677..fcc7530 100644 --- a/php/src/web/curl/curl.php +++ b/php/src/web/curl/curl.php @@ -12,11 +12,11 @@ class curl { if (!isset($curlOptions[CURLOPT_RETURNTRANSFER])) $curlOptions[CURLOPT_RETURNTRANSFER] = true; $extractHeaders = isset($curlOptions[CURLOPT_HEADER]) && $curlOptions[CURLOPT_HEADER]; $ch = curl_init(); - if ($ch === false) throw new CurlException(null, "init"); + if ($ch === false) throw new CurlException(null, "erreur curl lors de l'initialisation"); curl_setopt_array($ch, $curlOptions); try { $result = curl_exec($ch); - if ($result === false) throw new CurlException($ch); + if ($result === false) throw new CurlException($ch, "erreur curl lors du téléchargement"); if ($extractHeaders) { $info = curl_getinfo($ch); $headersSize = $info["header_size"]; diff --git a/php/tbin/sendmail.php b/php/tbin/sendmail.php index 89f1c01..0b20a6a 100755 --- a/php/tbin/sendmail.php +++ b/php/tbin/sendmail.php @@ -3,6 +3,7 @@ require __DIR__.'/../vendor/autoload.php'; use nulib\app\cli\Application; +use nulib\cv; use nulib\mail\mailer; use nulib\ValueException; @@ -20,10 +21,8 @@ Application::run(new class extends Application { protected $to, $cc, $bcc, $from; function main() { - $subject = $this->args[0] ?? null; - ValueException::check_null($subject, "subject"); - $body = $this->args[1] ?? null; - ValueException::check_null($body, "body"); + $subject = cv::not_null($this->args[0] ?? null, "subject"); + $body = cv::not_null($this->args[1] ?? null, "body"); mailer::send($this->to, $subject, $body, $this->cc, $this->bcc, $this->from); } }); diff --git a/php/tbin/test-exceptions.php b/php/tbin/test-exceptions.php new file mode 100755 index 0000000..ad17757 --- /dev/null +++ b/php/tbin/test-exceptions.php @@ -0,0 +1,65 @@ +#!/usr/bin/php + "tester l'affichage des exception", + + "merge" => parent::ARGS, + ]; + + function fart(): void { + throw new RuntimeException("fart"); + } + + function prout(): void { + try { + $this->fart(); + } catch (Exception $e) { + throw new RuntimeException("prout", $e->getCode(), $e); + } + } + + function main() { + try { + throw new Exception("exception normale"); + } catch (Exception $e) { + msg::info("summary: ". exceptions::get_summary($e)); + msg::error($e); + } + try { + try { + $this->prout(); + } catch (Exception $e) { + throw new Exception("exception normale", $e->getCode(), $e); + } + } catch (Exception $e) { + msg::info("summary: ". exceptions::get_summary($e)); + msg::error($e); + } + try { + throw exceptions::invalid_value("valeur", $kind) + ->setTechMessage("message technique"); + } catch (Exception $e) { + msg::info("summary: ". exceptions::get_summary($e)); + msg::error($e); + } + try { + try { + $this->prout(); + } catch (Exception $e) { + throw exceptions::invalid_value("valeur", $kind, null, $e) + ->setTechMessage("message technique"); + } + } catch (Exception $e) { + msg::info("summary: ". exceptions::get_summary($e)); + msg::error($e); + } + } +});