maj nur-sery
This commit is contained in:
parent
08a1ce49c5
commit
6755323cc5
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Codeception">
|
||||
<option name="configurations">
|
||||
<list>
|
||||
<Configuration>
|
||||
<option name="path" value="$PROJECT_DIR$/tests" />
|
||||
</Configuration>
|
||||
<Configuration>
|
||||
<option name="path" value="$PROJECT_DIR$/tests" />
|
||||
</Configuration>
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -2,15 +2,10 @@
|
|||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/php/src_base" isTestSource="false" packagePrefix="nulib\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/php/tests" isTestSource="true" packagePrefix="nulib\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/php/src_output" isTestSource="false" packagePrefix="nulib\output\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/php/src_web" isTestSource="false" packagePrefix="nulib\web\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/php/src_ref" isTestSource="false" packagePrefix="nulib\ref\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/php/src_php" isTestSource="false" packagePrefix="nulib\php\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/php/src_file" isTestSource="false" packagePrefix="nulib\file\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/php/src_os" isTestSource="false" packagePrefix="nulib\os\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/php/src_values" isTestSource="false" packagePrefix="nulib\values\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/php/src" isTestSource="false" packagePrefix="nulib\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="PHPSpec">
|
||||
<suites>
|
||||
<PhpSpecSuiteConfiguration>
|
||||
<option name="myPath" value="$PROJECT_DIR$" />
|
||||
</PhpSpecSuiteConfiguration>
|
||||
<PhpSpecSuiteConfiguration>
|
||||
<option name="myPath" value="$PROJECT_DIR$" />
|
||||
</PhpSpecSuiteConfiguration>
|
||||
</suites>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="PHPUnit">
|
||||
<option name="directories">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/tests" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
namespace nulib;
|
||||
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Class A: gestion de tableaux ou d'instances de {@link IArrayWrapper}
|
||||
*
|
||||
* contrairement à {@link cl}, les méthodes de cette classes sont plutôt conçues
|
||||
* pour modifier le tableau en place
|
||||
*/
|
||||
class A {
|
||||
/**
|
||||
* s'assurer que $array est un array non null. retourner true si $array n'a
|
||||
* pas été modifié (s'il était déjà un array), false sinon.
|
||||
*/
|
||||
static final function ensure_array(&$array): bool {
|
||||
if (is_array($array)) return true;
|
||||
if ($array instanceof IArrayWrapper) $array = $array->wrappedArray();
|
||||
if ($array === null || $array === false) $array = [];
|
||||
elseif ($array instanceof Traversable) $array = iterator_to_array($array);
|
||||
else $array = [$array];
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* s'assurer que $array est un array s'il est non null. retourner true si
|
||||
* $array n'a pas été modifié (s'il était déjà un array ou s'il valait null).
|
||||
*/
|
||||
static final function ensure_narray(&$array): bool {
|
||||
if ($array === null || is_array($array)) return true;
|
||||
if ($array instanceof IArrayWrapper) $array = $array->wrappedArray();
|
||||
if ($array === false) $array = [];
|
||||
elseif ($array instanceof Traversable) $array = iterator_to_array($array);
|
||||
else $array = [$array];
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
namespace nulib;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Class DataException: exception générique concernant l'accès à des données
|
||||
*/
|
||||
class DataException extends RuntimeException {
|
||||
static final function no_more_data(): self {
|
||||
return new self("no more data");
|
||||
}
|
||||
|
||||
static final function ensure_not_eof($data, bool $throw=true, $eof=false) {
|
||||
if (!$throw) return null;
|
||||
elseif ($data !== $eof) return $data;
|
||||
else throw self::no_more_data();
|
||||
}
|
||||
}
|
|
@ -8,8 +8,8 @@ use Throwable;
|
|||
* quitter normalement, avec éventuellement un code d'erreur.
|
||||
*/
|
||||
class ExitException extends UserException {
|
||||
function __construct(int $exitcode=0, $user_message=null, Throwable $previous=null) {
|
||||
parent::__construct($user_message, null, $exitcode, $previous);
|
||||
function __construct(int $exitcode=0, $userMessage=null, Throwable $previous=null) {
|
||||
parent::__construct($userMessage, null, $exitcode, $previous);
|
||||
}
|
||||
|
||||
function isError(): bool {
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
namespace nulib;
|
||||
|
||||
/**
|
||||
* Interface IArrayWrapper: un objet qui encapsule un array auquel on peut
|
||||
* accéder
|
||||
*/
|
||||
interface IArrayWrapper {
|
||||
/** retourne une référence sur l'array encapsulé */
|
||||
function &wrappedArray(): ?array;
|
||||
}
|
|
@ -75,12 +75,13 @@ class UserException extends RuntimeException {
|
|||
return implode("\n", $tbs);
|
||||
}
|
||||
|
||||
function __construct($user_message, $tech_message=null, $code=0, ?Throwable $previous=null) {
|
||||
$this->userMessage = $user_message;
|
||||
if ($tech_message === null) $tech_message = $user_message;
|
||||
parent::__construct($tech_message, $code, $previous);
|
||||
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 {
|
||||
|
|
|
@ -5,7 +5,11 @@ use ArrayAccess;
|
|||
use Traversable;
|
||||
|
||||
/**
|
||||
* Class cl: gestion de tableau de valeurs scalaires
|
||||
* Class cl: gestion de tableaux ou d'instances de {@link ArrayAccess} le cas
|
||||
* échéant
|
||||
*
|
||||
* contrairement à {@link A}, les méthodes de cette classes sont plutôt conçues
|
||||
* pour retourner un nouveau tableau
|
||||
*/
|
||||
class cl {
|
||||
/** retourner un array non null à partir de $array */
|
||||
|
@ -24,30 +28,6 @@ class cl {
|
|||
else return [$array];
|
||||
}
|
||||
|
||||
/**
|
||||
* s'assurer que $array est un array non null. retourner true si $array n'a
|
||||
* pas été modifié (s'il était déjà un array), false sinon.
|
||||
*/
|
||||
static final function ensure_array(&$array): bool {
|
||||
if (is_array($array)) return true;
|
||||
elseif ($array === null || $array === false) $array = [];
|
||||
elseif ($array instanceof Traversable) $array = iterator_to_array($array);
|
||||
else $array = [$array];
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* s'assurer que $array est un array s'il est non null. retourner true si
|
||||
* $array n'a pas été modifié (s'il était déjà un array ou s'il valait null).
|
||||
*/
|
||||
static final function ensure_narray(&$array): bool {
|
||||
if ($array === null || is_array($array)) return true;
|
||||
elseif ($array === false) $array = [];
|
||||
elseif ($array instanceof Traversable) $array = iterator_to_array($array);
|
||||
else $array = [$array];
|
||||
return false;
|
||||
}
|
||||
|
||||
/** tester si $array a au moins une clé numérique */
|
||||
static final function have_num_keys(?array $array): bool {
|
||||
if ($array === null) return false;
|
||||
|
@ -159,7 +139,7 @@ class cl {
|
|||
static final function merge(...$arrays): ?array {
|
||||
$merges = [];
|
||||
foreach ($arrays as $array) {
|
||||
self::ensure_narray($array);
|
||||
A::ensure_narray($array);
|
||||
if ($array !== null) $merges[] = $array;
|
||||
}
|
||||
return $merges? array_merge(...$merges): null;
|
||||
|
@ -170,7 +150,7 @@ class cl {
|
|||
/**
|
||||
* vérifier que le chemin $keys existe dans le tableau $array
|
||||
*
|
||||
* si $keys est vide ou null, retourner true
|
||||
* si $pkey est vide ou null, retourner true
|
||||
*/
|
||||
static final function phas($array, $pkey): bool {
|
||||
# optimisations
|
||||
|
@ -214,7 +194,7 @@ class cl {
|
|||
/**
|
||||
* obtenir la valeur correspondant au chemin $keys dans $array
|
||||
*
|
||||
* si $keys est vide ou null, retourner $default
|
||||
* si $pkey est vide ou null, retourner $default
|
||||
*/
|
||||
static final function pget($array, $pkey, $default=null) {
|
||||
# optimisations
|
||||
|
@ -264,7 +244,7 @@ class cl {
|
|||
* - pset($array, ["a", "b", ""], $value) est équivalent à $array["a"]["b"][] = $value
|
||||
* la clé "" n'a pas de propriété particulière quand elle n'est pas en dernière position
|
||||
*
|
||||
* si $keys est vide ou null, $array est remplacé par $value
|
||||
* si $pkey est vide ou null, $array est remplacé par $value
|
||||
*/
|
||||
static final function pset(&$array, $pkey, $value): void {
|
||||
# optimisations
|
||||
|
@ -281,7 +261,7 @@ class cl {
|
|||
$pkey = explode(".", strval($pkey));
|
||||
}
|
||||
# pset
|
||||
self::ensure_array($array);
|
||||
A::ensure_array($array);
|
||||
$current =& $array;
|
||||
$key = null;
|
||||
$last = count($pkey) - 1;
|
||||
|
@ -297,7 +277,7 @@ class cl {
|
|||
$current = [$current];
|
||||
}
|
||||
} else {
|
||||
self::ensure_array($current[$key]);
|
||||
A::ensure_array($current[$key]);
|
||||
$current =& $current[$key];
|
||||
}
|
||||
$i++;
|
||||
|
@ -318,7 +298,7 @@ class cl {
|
|||
* supprimer la valeur au chemin de clé $keys dans $array
|
||||
*
|
||||
* si $array vaut null ou false, sa valeur est inchangée.
|
||||
* si $keys est vide ou null, $array devient null
|
||||
* si $pkey est vide ou null, $array devient null
|
||||
*/
|
||||
static final function pdel(&$array, $pkey): void {
|
||||
# optimisations
|
||||
|
@ -334,7 +314,7 @@ class cl {
|
|||
$pkey = explode(".", strval($pkey));
|
||||
}
|
||||
# pdel
|
||||
self::ensure_array($array);
|
||||
A::ensure_array($array);
|
||||
$current =& $array;
|
||||
$key = null;
|
||||
$last = count($pkey) - 1;
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
namespace nulib\db;
|
||||
|
||||
/**
|
||||
* Class Capacitor: un objet permettant d'attaquer un canal spécique d'une
|
||||
* instance de {@link CapacitorStorage}
|
||||
*/
|
||||
class Capacitor {
|
||||
function __construct(CapacitorStorage $storage, CapacitorChannel $channel, bool $ensureExists=true) {
|
||||
$this->storage = $storage;
|
||||
$this->channel = $channel;
|
||||
if ($ensureExists) $this->ensureExists();
|
||||
}
|
||||
|
||||
/** @var CapacitorStorage */
|
||||
protected $storage;
|
||||
|
||||
/** @var CapacitorChannel */
|
||||
protected $channel;
|
||||
|
||||
function exists(): bool {
|
||||
return $this->storage->_exists($this->channel);
|
||||
}
|
||||
|
||||
function ensureExists(): void {
|
||||
$this->storage->_ensureExists($this->channel);
|
||||
}
|
||||
|
||||
function reset(): void {
|
||||
$this->storage->_reset($this->channel);
|
||||
}
|
||||
|
||||
function charge($item, ?callable $func=null, ?array $args=null): int {
|
||||
return $this->storage->_charge($this->channel, $item, $func, $args);
|
||||
}
|
||||
|
||||
function count($filter=null): int {
|
||||
return $this->storage->_count($this->channel, $filter);
|
||||
}
|
||||
|
||||
function discharge($filter=null, ?bool $reset=null): iterable {
|
||||
return $this->storage->_discharge($this->channel, $filter, $reset);
|
||||
}
|
||||
|
||||
function get($filter) {
|
||||
return $this->storage->_get($this->channel, $filter);
|
||||
}
|
||||
|
||||
function each($filter, ?callable $func=null, ?array $args=null): int {
|
||||
return $this->storage->_each($this->channel, $filter, $func, $args);
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
$this->storage->close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
namespace nulib\db;
|
||||
|
||||
/**
|
||||
* Class CapacitorChannel: un canal d'une instance de {@link ICapacitor}
|
||||
*/
|
||||
class CapacitorChannel {
|
||||
const NAME = null;
|
||||
|
||||
const EACH_COMMIT_THRESHOLD = 100;
|
||||
|
||||
static function verifix_name(?string $name): string {
|
||||
if ($name === null) $name = "default";
|
||||
return strtolower($name);
|
||||
}
|
||||
|
||||
function __construct(?string $name=null, ?int $eachCommitThreshold=null) {
|
||||
$this->name = self::verifix_name($name ?? static::NAME);
|
||||
$this->eachCommitThreshold = $eachCommitThreshold ?? static::EACH_COMMIT_THRESHOLD;
|
||||
$this->created = false;
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
protected $name;
|
||||
|
||||
function getName(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ?int nombre maximum de modifications dans une transaction avant un
|
||||
* commit automatique dans {@link Capacitor::each()}. Utiliser null pour
|
||||
* désactiver la fonctionnalité.
|
||||
*/
|
||||
protected $eachCommitThreshold;
|
||||
|
||||
function getEachCommitThreshold(): ?int {
|
||||
return $this->eachCommitThreshold;
|
||||
}
|
||||
|
||||
function getTableName(): string {
|
||||
return $this->name."_channel";
|
||||
}
|
||||
|
||||
protected $created;
|
||||
|
||||
function isCreated(): bool {
|
||||
return $this->created;
|
||||
}
|
||||
|
||||
function setCreated(bool $created=true): void {
|
||||
$this->created = $created;
|
||||
}
|
||||
|
||||
/**
|
||||
* retourner un ensemble de définitions pour des colonnes supplémentaires à
|
||||
* insérer lors du chargement d'une valeur
|
||||
*
|
||||
* la colonne "_id" de définition "integer primary key autoincrement" est la
|
||||
* clé primaire par défaut. elle peut être redéfinie, et dans ce cas la valeur
|
||||
* à utiliser doit être retournée par {@link getKeyValues()}
|
||||
*/
|
||||
function getKeyDefinitions(): ?array {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* calculer les valeurs des colonnes supplémentaires à insérer pour le
|
||||
* chargement de $item
|
||||
*
|
||||
* Si "_id" est retourné, la ligne existante est mise à jour le cas échéant.
|
||||
*/
|
||||
function getKeyValues($item): ?array {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* méthode appelée lors du chargement d'un élément avec
|
||||
* {@link Capacitor::charge()}
|
||||
*
|
||||
* @param mixed $item l'élément à charger
|
||||
* @param array $values les valeurs calculées par {@link getKeyValues()}
|
||||
* @param ?array $row la ligne à mettre à jour. vaut null s'il faut insérer
|
||||
* une nouvelle ligne
|
||||
* @return ?array le cas échéant, un tableau non null à marger dans $values et
|
||||
* utiliser pour provisionner la ligne nouvelle créée, ou mettre à jour la
|
||||
* ligne existante
|
||||
*
|
||||
* Si $item est modifié dans cette méthode, il est possible de le retourner
|
||||
* avec la clé "_item" pour mettre à jour la ligne correspondante
|
||||
*/
|
||||
function onCharge($item, array $values, ?array $row): ?array {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* méthode appelée lors du parcours des éléments avec
|
||||
* {@link Capacitor::each()}
|
||||
*
|
||||
* @param mixed $item l'élément courant
|
||||
* @param ?array $row la ligne à mettre à jour
|
||||
* @return ?array le cas échéant, un tableau non null utilisé pour mettre à
|
||||
* jour la ligne courante
|
||||
*
|
||||
* Si $item est modifié dans cette méthode, il est possible de le retourner
|
||||
* avec la clé "_item" pour mettre à jour la ligne correspondante
|
||||
*/
|
||||
function onEach($item, array $row): ?array {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
namespace nulib\db;
|
||||
|
||||
/**
|
||||
* Class CapacitorStorage: objet permettant d'accumuler des données pour les
|
||||
* réutiliser plus tard
|
||||
*/
|
||||
abstract class CapacitorStorage {
|
||||
abstract protected function getChannel(?string $name): CapacitorChannel;
|
||||
|
||||
abstract function _exists(CapacitorChannel $channel): bool;
|
||||
|
||||
/** tester si le canal spécifié existe */
|
||||
function exists(?string $channel): bool {
|
||||
return $this->_exists($this->getChannel($channel));
|
||||
}
|
||||
|
||||
abstract function _ensureExists(CapacitorChannel $channel): void;
|
||||
|
||||
/** s'assurer que le canal spécifié existe */
|
||||
function ensureExists(?string $channel): void {
|
||||
$this->_ensureExists($this->getChannel($channel));
|
||||
}
|
||||
|
||||
abstract function _reset(CapacitorChannel $channel): void;
|
||||
|
||||
/** supprimer le canal spécifié */
|
||||
function reset(?string $channel): void {
|
||||
$this->_reset($this->getChannel($channel));
|
||||
}
|
||||
|
||||
abstract function _charge(CapacitorChannel $channel, $item, ?callable $func, ?array $args): int;
|
||||
|
||||
/**
|
||||
* charger une valeur dans le canal
|
||||
*
|
||||
* Si $func!==null, après avoir calculé les valeurs des clés supplémentaires
|
||||
* avec {@link CapacitorChannel::getKeyValues()}, la fonction est appelée avec
|
||||
* la signature ($item, $keyValues, $row, ...$args)
|
||||
* Si la fonction retourne un tableau, il est utilisé pour modifier les valeurs
|
||||
* insérées/mises à jour
|
||||
*
|
||||
* @return int 1 si l'objet a été chargé ou mis à jour, 0 s'il existait
|
||||
* déjà à l'identique dans le canal
|
||||
*/
|
||||
function charge(?string $channel, $item, ?callable $func=null, ?array $args=null): int {
|
||||
return $this->_charge($this->getChannel($channel), $item, $func, $args);
|
||||
}
|
||||
|
||||
abstract function _count(CapacitorChannel $channel, $filter): int;
|
||||
|
||||
/** indiquer le nombre d'éléments du canal spécifié */
|
||||
function count(?string $channel, $filter=null): int {
|
||||
return $this->_count($this->getChannel($channel), $filter);
|
||||
}
|
||||
|
||||
abstract function _discharge(CapacitorChannel $channel, $filter, ?bool $reset): iterable;
|
||||
|
||||
/** décharger les données du canal spécifié */
|
||||
function discharge(?string $channel, $filter=null, ?bool $reset=null): iterable {
|
||||
return $this->_discharge($this->getChannel($channel), $filter, $reset);
|
||||
}
|
||||
|
||||
abstract function _get(CapacitorChannel $channel, $filter);
|
||||
|
||||
/**
|
||||
* obtenir l'élément identifié par les clés spécifiées sur le canal spécifié
|
||||
*
|
||||
* si $filter n'est pas un tableau, il est transformé en ["_id" => $filter]
|
||||
*/
|
||||
function get(?string $channel, $filter) {
|
||||
return $this->_get($this->getChannel($channel), $filter);
|
||||
}
|
||||
|
||||
abstract function _each(CapacitorChannel $channel, $filter, ?callable $func, ?array $args): int;
|
||||
|
||||
/**
|
||||
* appeler une fonction pour chaque élément du canal spécifié.
|
||||
*
|
||||
* $filter permet de filtrer parmi les élements chargés
|
||||
*
|
||||
* $func est appelé avec la signature ($item, $row, ...$args). si la fonction
|
||||
* retourne un tableau, il est utilisé pour mettre à jour la ligne
|
||||
*
|
||||
* @return int le nombre de lignes parcourues
|
||||
*/
|
||||
function each(?string $channel, $filter, ?callable $func=null, ?array $args=null): int {
|
||||
return $this->_each($this->getChannel($channel), $filter, $func, $args);
|
||||
}
|
||||
|
||||
abstract function close(): void;
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use Generator;
|
||||
use nulib\cl;
|
||||
use SQLite3;
|
||||
use SQLite3Result;
|
||||
use SQLite3Stmt;
|
||||
|
||||
/**
|
||||
* Class Sqlite: frontend vers une base de données sqlite3
|
||||
*/
|
||||
class Sqlite {
|
||||
static function with($sqlite, ?array $params=null): self {
|
||||
if ($sqlite instanceof static) {
|
||||
return $sqlite;
|
||||
} elseif ($sqlite instanceof self) {
|
||||
# recréer avec les mêmes paramètres
|
||||
return new static(null, cl::merge([
|
||||
"file" => $sqlite->file,
|
||||
"flags" => $sqlite->flags,
|
||||
"encryption_key" => $sqlite->encryptionKey,
|
||||
"allow_wal" => $sqlite->allowWal,
|
||||
"config" => $sqlite->config,
|
||||
"migrate" => $sqlite->migration,
|
||||
], $params));
|
||||
} elseif (is_array($sqlite)) {
|
||||
return new static(null, cl::merge($sqlite, $params));
|
||||
} else {
|
||||
return new static($sqlite, $params);
|
||||
}
|
||||
}
|
||||
|
||||
static function config_enableExceptions(self $sqlite): void {
|
||||
$sqlite->db->enableExceptions(true);
|
||||
}
|
||||
|
||||
static function config_enableWalIfAllowed(self $sqlite): void {
|
||||
if ($sqlite->isWalAllowed()) {
|
||||
$sqlite->db->exec("PRAGMA journal_mode=WAL");
|
||||
}
|
||||
}
|
||||
|
||||
const ALLOW_WAL = null;
|
||||
|
||||
const CONFIG = [
|
||||
[self::class, "config_enableExceptions"],
|
||||
[self::class, "config_enableWalIfAllowed"],
|
||||
];
|
||||
|
||||
const MIGRATE = null;
|
||||
|
||||
const SCHEMA = [
|
||||
"file" => ["string", ""],
|
||||
"flags" => ["int", SQLITE3_OPEN_READWRITE + SQLITE3_OPEN_CREATE],
|
||||
"encryption_key" => ["string", ""],
|
||||
"allow_wal" => ["?bool"],
|
||||
"config" => ["?array|callable"],
|
||||
"migrate" => ["?array|string|callable"],
|
||||
"auto_open" => ["bool", true],
|
||||
];
|
||||
|
||||
function __construct(?string $file=null, ?array $params=null) {
|
||||
if ($file !== null) $params["file"] = $file;
|
||||
##schéma
|
||||
$defaultFile = self::SCHEMA["file"][1];
|
||||
$this->file = $file = strval($params["file"] ?? $defaultFile);
|
||||
$inMemory = $file === ":memory:";
|
||||
#
|
||||
$defaultFlags = self::SCHEMA["flags"][1];
|
||||
$this->flags = intval($params["flags"] ?? $defaultFlags);
|
||||
#
|
||||
$defaultEncryptionKey = self::SCHEMA["encryption_key"][1];
|
||||
$this->encryptionKey = strval($params["encryption_key"] ?? $defaultEncryptionKey);
|
||||
#
|
||||
$defaultAllowWal = static::ALLOW_WAL ?? !$inMemory;
|
||||
$this->allowWal = $params["allow_wal"] ?? $defaultAllowWal;
|
||||
# configuration
|
||||
$this->config = $params["config"] ?? static::CONFIG;
|
||||
# migrations
|
||||
$this->migration = $params["migrate"] ?? static::MIGRATE;
|
||||
#
|
||||
$defaultAutoOpen = self::SCHEMA["auto_open"][1];
|
||||
if ($params["auto_open"] ?? $defaultAutoOpen) {
|
||||
$this->open();
|
||||
}
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
protected $file;
|
||||
|
||||
/** @var int */
|
||||
protected $flags;
|
||||
|
||||
/** @var string */
|
||||
protected $encryptionKey;
|
||||
|
||||
/** @var bool */
|
||||
protected $allowWal;
|
||||
|
||||
/** vérifier s'il est autorisé de configurer le mode WAL */
|
||||
function isWalAllowed(): bool {
|
||||
return $this->allowWal;
|
||||
}
|
||||
|
||||
/** @var array|string|callable */
|
||||
protected $config;
|
||||
|
||||
/** @var array|string|callable */
|
||||
protected $migration;
|
||||
|
||||
/** @var SQLite3 */
|
||||
protected $db;
|
||||
|
||||
function open(): self {
|
||||
if ($this->db === null) {
|
||||
$this->db = new SQLite3($this->file, $this->flags, $this->encryptionKey);
|
||||
_config::with($this->config)->configure($this);
|
||||
_migration::with($this->migration)->migrate($this);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
if ($this->db !== null) {
|
||||
$this->db->close();
|
||||
$this->db = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkStmt($stmt): SQLite3Stmt {
|
||||
return SqliteException::check($this->db, $stmt);
|
||||
}
|
||||
|
||||
protected function checkResult($result): SQLite3Result {
|
||||
return SqliteException::check($this->db, $result);
|
||||
}
|
||||
|
||||
protected function db(): SQLite3 {
|
||||
$this->open();
|
||||
return $this->db;
|
||||
}
|
||||
|
||||
function _exec(string $query): bool {
|
||||
return $this->db()->exec($query);
|
||||
}
|
||||
|
||||
function exec($query, ?array $params=null): bool {
|
||||
$db = $this->db();
|
||||
$query = new _query($query, $params);
|
||||
if ($query->useStmt($db, $stmt, $sql)) {
|
||||
try {
|
||||
return $stmt->execute()->finalize();
|
||||
} finally {
|
||||
$stmt->close();
|
||||
}
|
||||
} else {
|
||||
return $db->exec($sql);
|
||||
}
|
||||
}
|
||||
|
||||
function beginTransaction(): void {
|
||||
$this->db()->exec("begin");
|
||||
}
|
||||
|
||||
function commit(): void {
|
||||
$this->db()->exec("commit");
|
||||
}
|
||||
|
||||
function rollback(): void {
|
||||
$this->db()->exec("commit");
|
||||
}
|
||||
|
||||
function _get(string $query, bool $entireRow=false) {
|
||||
return $this->db()->querySingle($query, $entireRow);
|
||||
}
|
||||
|
||||
function get($query, ?array $params=null, bool $entireRow=false) {
|
||||
$db = $this->db();
|
||||
$query = new _query($query, $params);
|
||||
if ($query->useStmt($db, $stmt, $sql)) {
|
||||
try {
|
||||
$result = $this->checkResult($stmt->execute());
|
||||
try {
|
||||
$row = $result->fetchArray(SQLITE3_ASSOC);
|
||||
if ($row === false) return null;
|
||||
elseif ($entireRow) return $row;
|
||||
else return cl::first($row);
|
||||
} finally {
|
||||
$result->finalize();
|
||||
}
|
||||
} finally {
|
||||
$stmt->close();
|
||||
}
|
||||
} else {
|
||||
return $db->querySingle($sql, $entireRow);
|
||||
}
|
||||
}
|
||||
|
||||
function one($query, ?array $params=null): ?array {
|
||||
return $this->get($query, $params, true);
|
||||
}
|
||||
|
||||
protected function _fetchResult(SQLite3Result $result, ?SQLite3Stmt $stmt=null): Generator {
|
||||
try {
|
||||
while (($row = $result->fetchArray(SQLITE3_ASSOC)) !== false) {
|
||||
yield $row;
|
||||
}
|
||||
} finally {
|
||||
$result->finalize();
|
||||
if ($stmt !== null) $stmt->close();
|
||||
}
|
||||
}
|
||||
|
||||
function all($query, ?array $params=null): iterable {
|
||||
$db = $this->db();
|
||||
$query = new _query($query, $params);
|
||||
if ($query->useStmt($db, $stmt, $sql)) {
|
||||
$result = $this->checkResult($stmt->execute());
|
||||
return $this->_fetchResult($result, $stmt);
|
||||
} else {
|
||||
$result = $this->checkResult($db->query($sql));
|
||||
return $this->_fetchResult($result);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\db\CapacitorChannel;
|
||||
use nulib\db\CapacitorStorage;
|
||||
use nulib\php\func;
|
||||
use nulib\ValueException;
|
||||
|
||||
/**
|
||||
* Class SqliteCapacitor
|
||||
*/
|
||||
class SqliteCapacitor extends CapacitorStorage {
|
||||
function __construct($sqlite) {
|
||||
$this->sqlite = Sqlite::with($sqlite);
|
||||
}
|
||||
|
||||
/** @var Sqlite */
|
||||
protected $sqlite;
|
||||
|
||||
function sqlite(): Sqlite {
|
||||
return $this->sqlite;
|
||||
}
|
||||
|
||||
protected function _create(CapacitorChannel $channel): void {
|
||||
if (!$channel->isCreated()) {
|
||||
$columns = cl::merge([
|
||||
"_id" => "integer primary key autoincrement",
|
||||
"_item" => "text",
|
||||
"_sum" => "varchar(40)",
|
||||
"_created" => "datetime",
|
||||
"_modified" => "datetime",
|
||||
], $channel->getKeyDefinitions());
|
||||
$this->sqlite->exec([
|
||||
"create table if not exists",
|
||||
"table" => $channel->getTableName(),
|
||||
"cols" => $columns,
|
||||
]);
|
||||
$channel->setCreated();
|
||||
}
|
||||
}
|
||||
|
||||
/** @var CapacitorChannel[] */
|
||||
protected $channels;
|
||||
|
||||
function addChannel(CapacitorChannel $channel): CapacitorChannel {
|
||||
$this->_create($channel);
|
||||
$this->channels[$channel->getName()] = $channel;
|
||||
return $channel;
|
||||
}
|
||||
|
||||
protected function getChannel(?string $name): CapacitorChannel {
|
||||
$name = CapacitorChannel::verifix_name($name);
|
||||
$channel = $this->channels[$name] ?? null;
|
||||
if ($channel === null) {
|
||||
$channel = $this->addChannel(new CapacitorChannel($name));
|
||||
}
|
||||
return $channel;
|
||||
}
|
||||
|
||||
function _exists(CapacitorChannel $channel): bool {
|
||||
$tableName = $this->sqlite->get([
|
||||
"select name from sqlite_schema",
|
||||
"where" => [
|
||||
"name" => $channel->getTableName(),
|
||||
],
|
||||
]);
|
||||
return $tableName !== null;
|
||||
}
|
||||
|
||||
function _ensureExists(CapacitorChannel $channel): void {
|
||||
$this->_create($channel);
|
||||
}
|
||||
|
||||
function _reset(CapacitorChannel $channel): void {
|
||||
$this->sqlite->exec([
|
||||
"drop table if exists",
|
||||
$channel->getTableName(),
|
||||
]);
|
||||
$channel->setCreated(false);
|
||||
}
|
||||
|
||||
function _charge(CapacitorChannel $channel, $item, ?callable $func, ?array $args): int {
|
||||
$this->_create($channel);
|
||||
$now = date("Y-m-d H:i:s");
|
||||
$_item = serialize($item);
|
||||
$_sum = sha1($_item);
|
||||
$values = cl::merge([
|
||||
"_item" => $_item,
|
||||
"_sum" => $_sum,
|
||||
], $channel->getKeyValues($item));
|
||||
$row = null;
|
||||
$id = $values["_id"] ?? null;
|
||||
if ($id !== null) {
|
||||
# modification
|
||||
$row = $this->sqlite->one([
|
||||
"select _item, _sum, _created, _modified",
|
||||
"from" => $channel->getTableName(),
|
||||
"where" => ["_id" => $id],
|
||||
]);
|
||||
}
|
||||
$insert = null;
|
||||
if ($row === null) {
|
||||
# création
|
||||
$values = cl::merge($values, [
|
||||
"_created" => $now,
|
||||
"_modified" => $now,
|
||||
]);
|
||||
$insert = true;
|
||||
} elseif ($_sum !== $row["_sum"]) {
|
||||
# modification
|
||||
$values = cl::merge($values, [
|
||||
"_modified" => $now,
|
||||
]);
|
||||
$insert = false;
|
||||
}
|
||||
|
||||
if ($func === null) $func = [$channel, "onCharge"];
|
||||
$onCharge = func::_prepare($func);
|
||||
$args ??= [];
|
||||
$updates = func::_call($onCharge, [$item, $values, $row, ...$args]);
|
||||
if (is_array($updates)) {
|
||||
if (array_key_exists("_item", $updates)) {
|
||||
$_item = serialize($updates["_item"]);
|
||||
$updates["_item"] = $_item;
|
||||
$updates["_sum"] = sha1($_item);
|
||||
if (!array_key_exists("_modified", $updates)) {
|
||||
$updates["_modified"] = $now;
|
||||
}
|
||||
}
|
||||
$values = cl::merge($values, $updates);
|
||||
}
|
||||
|
||||
if ($insert === null) {
|
||||
# aucune modification
|
||||
return 0;
|
||||
} elseif ($insert) {
|
||||
$this->sqlite->exec([
|
||||
"insert",
|
||||
"into" => $channel->getTableName(),
|
||||
"values" => $values,
|
||||
]);
|
||||
} else {
|
||||
$this->sqlite->exec([
|
||||
"update",
|
||||
"table" => $channel->getTableName(),
|
||||
"values" => $values,
|
||||
"where" => ["_id" => $id],
|
||||
]);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
function _count(CapacitorChannel $channel, $filter): int {
|
||||
if ($filter !== null && !is_array($filter)) $filter = ["_id" => $filter];
|
||||
return $this->sqlite->get([
|
||||
"select count(*)",
|
||||
"from" => $channel->getTableName(),
|
||||
"where" => $filter,
|
||||
]);
|
||||
}
|
||||
|
||||
function _discharge(CapacitorChannel $channel, $filter, ?bool $reset): iterable {
|
||||
if ($filter !== null && !is_array($filter)) $filter = ["_id" => $filter];
|
||||
if ($reset === null) $reset = $filter === null;
|
||||
$rows = $this->sqlite->all([
|
||||
"select _item",
|
||||
"from" => $channel->getTableName(),
|
||||
"where" => $filter,
|
||||
]);
|
||||
foreach ($rows as $row) {
|
||||
$item = unserialize($row['_item']);
|
||||
yield $item;
|
||||
}
|
||||
if ($reset) $this->_reset($channel);
|
||||
}
|
||||
|
||||
function _get(CapacitorChannel $channel, $filter) {
|
||||
if ($filter === null) throw ValueException::null("keys");
|
||||
if (!is_array($filter)) $filter = ["_id" => $filter];
|
||||
$row = $this->sqlite->one([
|
||||
"select _item",
|
||||
"from" => $channel->getTableName(),
|
||||
"where" => $filter,
|
||||
]);
|
||||
if ($row === null) return null;
|
||||
else return unserialize($row["_item"]);
|
||||
}
|
||||
|
||||
function _each(CapacitorChannel $channel, $filter, ?callable $func, ?array $args): int {
|
||||
if ($func === null) $func = [$channel, "onEach"];
|
||||
$onEach = func::_prepare($func);
|
||||
if ($filter !== null && !is_array($filter)) $filter = ["_id" => $filter];
|
||||
$sqlite = $this->sqlite;
|
||||
$tableName = $channel->getTableName();
|
||||
$commited = false;
|
||||
$count = 0;
|
||||
$sqlite->beginTransaction();
|
||||
$commitThreshold = $channel->getEachCommitThreshold();
|
||||
try {
|
||||
$rows = $sqlite->all([
|
||||
"select",
|
||||
"from" => $tableName,
|
||||
"where" => $filter,
|
||||
]);
|
||||
$args ??= [];
|
||||
foreach ($rows as $row) {
|
||||
$item = unserialize($row['_item']);
|
||||
$updates = func::_call($onEach, [$item, $row, ...$args]);
|
||||
if (is_array($updates)) {
|
||||
if (array_key_exists("_item", $updates)) {
|
||||
$updates["_item"] = serialize($updates["_item"]);
|
||||
}
|
||||
$sqlite->exec([
|
||||
"update",
|
||||
"table" => $tableName,
|
||||
"values" => $updates,
|
||||
"where" => ["_id" => $row["_id"]],
|
||||
]);
|
||||
if ($commitThreshold !== null) {
|
||||
$commitThreshold--;
|
||||
if ($commitThreshold == 0) {
|
||||
$sqlite->commit();
|
||||
$commitThreshold = $channel->getEachCommitThreshold();
|
||||
}
|
||||
}
|
||||
}
|
||||
$count++;
|
||||
}
|
||||
$sqlite->commit();
|
||||
$commited = true;
|
||||
return $count;
|
||||
} finally {
|
||||
if (!$commited) $sqlite->rollback();
|
||||
}
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
$this->sqlite->close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
use SQLite3;
|
||||
|
||||
class SqliteException extends RuntimeException {
|
||||
static final function check(SQLite3 $db, $value, bool $throw=true) {
|
||||
if ($value !== false) return $value;
|
||||
elseif (!$throw) return null;
|
||||
else throw new static($db->lastErrorMsg(), $db->lastErrorCode());
|
||||
}
|
||||
|
||||
static final function wrap(Exception $e): self{
|
||||
return new static($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\php\func;
|
||||
|
||||
class _config {
|
||||
static function with($configs): self {
|
||||
if ($configs instanceof static) return $configs;
|
||||
return new static($configs);
|
||||
}
|
||||
|
||||
const CONFIG = null;
|
||||
|
||||
function __construct($configs) {
|
||||
if ($configs === null) $configs = static::CONFIG;
|
||||
if ($configs === null) $configs = [];
|
||||
elseif (is_string($configs)) $configs = [$configs];
|
||||
elseif (is_callable($configs)) $configs = [$configs];
|
||||
elseif (!is_array($configs)) $configs = [strval($configs)];
|
||||
$this->configs = $configs;
|
||||
}
|
||||
|
||||
/** @var array */
|
||||
protected $configs;
|
||||
|
||||
function configure(Sqlite $sqlite): void {
|
||||
foreach ($this->configs as $key => $config) {
|
||||
if (is_string($config) && !func::is_method($config)) {
|
||||
$sqlite->exec($config);
|
||||
} else {
|
||||
func::ensure_func($config, $this, $args);
|
||||
func::call($config, $sqlite, $key, ...$args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\php\func;
|
||||
|
||||
class _migration {
|
||||
static function with($migrations): self {
|
||||
if ($migrations instanceof static) {
|
||||
return $migrations;
|
||||
} elseif ($migrations instanceof self) {
|
||||
return new static($migrations->migrations);
|
||||
} else {
|
||||
return new static($migrations);
|
||||
}
|
||||
}
|
||||
|
||||
const MIGRATE = null;
|
||||
|
||||
function __construct($migrations) {
|
||||
if ($migrations === null) $migrations = static::MIGRATE;
|
||||
if ($migrations === null) $migrations = [];
|
||||
elseif (is_string($migrations)) $migrations = [$migrations];
|
||||
elseif (is_callable($migrations)) $migrations = [$migrations];
|
||||
elseif (!is_array($migrations)) $migrations = [strval($migrations)];
|
||||
$this->migrations = $migrations;
|
||||
}
|
||||
|
||||
/** @var callable[]|string[] */
|
||||
protected $migrations;
|
||||
|
||||
function migrate(Sqlite $sqlite): void {
|
||||
$sqlite->exec("create table if not exists _migration(key varchar primary key, value varchar not null, done integer default 0)");
|
||||
foreach ($this->migrations as $key => $migration) {
|
||||
$exists = $sqlite->get("select 1 from _migration where key = :key and done = 1", [
|
||||
"key" => $key,
|
||||
]);
|
||||
if (!$exists) {
|
||||
$sqlite->exec("insert or replace into _migration(key, value, done) values(:key, :value, :done)", [
|
||||
"key" => $key,
|
||||
"value" => $migration,
|
||||
"done" => 0,
|
||||
]);
|
||||
if (is_string($migration) && !func::is_method($migration)) {
|
||||
$sqlite->exec($migration);
|
||||
} else {
|
||||
func::ensure_func($migration, $this, $args);
|
||||
func::call($migration, $sqlite, $key, ...$args);
|
||||
}
|
||||
$sqlite->exec("update _migration set done = 1 where key = :key", [
|
||||
"key" => $key,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\str;
|
||||
use nulib\ValueException;
|
||||
use SQLite3;
|
||||
use SQLite3Stmt;
|
||||
|
||||
class _query {
|
||||
static function verifix(&$query, ?array &$params=null): void {
|
||||
if (is_array($query)) {
|
||||
$prefix = $query[0] ?? null;
|
||||
if ($prefix === null) {
|
||||
throw new ValueException("requête invalide");
|
||||
} elseif (_query_create::isa($prefix)) {
|
||||
$query = _query_create::parse($query, $params);
|
||||
} elseif (_query_select::isa($prefix)) {
|
||||
$query = _query_select::parse($query, $params);
|
||||
} elseif (_query_insert::isa($prefix)) {
|
||||
$query = _query_insert::parse($query, $params);
|
||||
} elseif (_query_update::isa($prefix)) {
|
||||
$query = _query_update::parse($query, $params);
|
||||
} elseif (_query_delete::isa($prefix)) {
|
||||
$query = _query_delete::parse($query, $params);
|
||||
} elseif (_query_generic::isa($prefix)) {
|
||||
$query = _query_generic::parse($query, $params);
|
||||
} else {
|
||||
throw SqliteException::wrap(ValueException::invalid_kind($query, "query"));
|
||||
}
|
||||
} elseif (!is_string($query)) {
|
||||
$query = strval($query);
|
||||
}
|
||||
}
|
||||
|
||||
protected static function consume(string $pattern, string &$string, ?array &$ms=null): bool {
|
||||
if (!preg_match("/^$pattern/i", $string, $ms)) return false;
|
||||
$string = substr($string, strlen($ms[0]));
|
||||
return true;
|
||||
}
|
||||
|
||||
/** fusionner toutes les parties séquentielles d'une requête */
|
||||
protected static function merge_seq(array $query): string {
|
||||
$index = 0;
|
||||
$sql = "";
|
||||
foreach ($query as $key => $value) {
|
||||
if ($key === $index) {
|
||||
$index++;
|
||||
if ($sql && !str::ends_with(" ", $sql) && !str::starts_with(" ", $value)) {
|
||||
$sql .= " ";
|
||||
}
|
||||
$sql .= $value;
|
||||
}
|
||||
}
|
||||
return $sql;
|
||||
}
|
||||
|
||||
protected static function is_sep(&$cond): bool {
|
||||
if (!is_string($cond)) return false;
|
||||
if (!preg_match('/^\s*(and|or|not)\s*$/i', $cond, $ms)) return false;
|
||||
$cond = $ms[1];
|
||||
return true;
|
||||
}
|
||||
|
||||
static function parse_conds(?array $conds, ?array &$sql, ?array &$params): void {
|
||||
if (!$conds) return;
|
||||
$sep = null;
|
||||
$index = 0;
|
||||
$condsql = [];
|
||||
foreach ($conds as $key => $cond) {
|
||||
if ($key === $index) {
|
||||
## séquentiel
|
||||
if ($index === 0 && self::is_sep($cond)) {
|
||||
$sep = $cond;
|
||||
} elseif (is_array($cond)) {
|
||||
# condition récursive
|
||||
self::parse_conds($cond, $condsql, $params);
|
||||
} else {
|
||||
# condition litérale
|
||||
$condsql[] = strval($cond);
|
||||
}
|
||||
$index++;
|
||||
} else {
|
||||
## associatif
|
||||
# paramètre
|
||||
$param = $key;
|
||||
if ($params !== null && array_key_exists($param, $params)) {
|
||||
$i = 1;
|
||||
while (array_key_exists("$key$i", $params)) {
|
||||
$i++;
|
||||
}
|
||||
$param = "$key$i";
|
||||
}
|
||||
# value ou [operator, value]
|
||||
if (is_array($cond)) {
|
||||
#XXX implémenter le support de ["between", lower, upper]
|
||||
# et aussi ["in", values]
|
||||
$op = null;
|
||||
$value = null;
|
||||
$condkeys = array_keys($cond);
|
||||
if (array_key_exists("op", $cond)) $op = $cond["op"];
|
||||
if (array_key_exists("value", $cond)) $value = $cond["value"];
|
||||
$condkey = 0;
|
||||
if ($op === null && array_key_exists($condkey, $condkeys)) {
|
||||
$op = $cond[$condkeys[$condkey]];
|
||||
$condkey++;
|
||||
}
|
||||
if ($value === null && array_key_exists($condkey, $condkeys)) {
|
||||
$value = $cond[$condkeys[$condkey]];
|
||||
$condkey++;
|
||||
}
|
||||
} else {
|
||||
$op = "=";
|
||||
$value = $cond;
|
||||
}
|
||||
$cond = [$key, $op];
|
||||
if ($value !== null) {
|
||||
$cond[] = ":$param";
|
||||
$params[$param] = $value;
|
||||
}
|
||||
$condsql[] = implode(" ", $cond);
|
||||
}
|
||||
}
|
||||
if ($sep === null) $sep = "and";
|
||||
$count = count($condsql);
|
||||
if ($count > 1) {
|
||||
$sql[] = "(" . implode(" $sep ", $condsql) . ")";
|
||||
} elseif ($count == 1) {
|
||||
$sql[] = $condsql[0];
|
||||
}
|
||||
}
|
||||
|
||||
static function parse_set_values(?array $values, ?array &$sql, ?array &$params): void {
|
||||
if (!$values) return;
|
||||
$index = 0;
|
||||
$parts = [];
|
||||
foreach ($values as $key => $part) {
|
||||
if ($key === $index) {
|
||||
## séquentiel
|
||||
if (is_array($part)) {
|
||||
# paramètres récursifs
|
||||
self::parse_set_values($part, $parts, $params);
|
||||
} else {
|
||||
# paramètre litéral
|
||||
$parts[] = strval($part);
|
||||
}
|
||||
$index++;
|
||||
} else {
|
||||
## associatif
|
||||
# paramètre
|
||||
$param = $key;
|
||||
if ($params !== null && array_key_exists($param, $params)) {
|
||||
$i = 1;
|
||||
while (array_key_exists("$key$i", $params)) {
|
||||
$i++;
|
||||
}
|
||||
$param = "$key$i";
|
||||
}
|
||||
# value
|
||||
$value = $part;
|
||||
$part = [$key, "="];
|
||||
if ($value === null) {
|
||||
$part[] = "null";
|
||||
} else {
|
||||
$part[] = ":$param";
|
||||
$params[$param] = $value;
|
||||
}
|
||||
$parts[] = implode(" ", $part);
|
||||
}
|
||||
}
|
||||
$sql = cl::merge($sql, $parts);
|
||||
}
|
||||
|
||||
protected static function check_eof(string $tmpsql, string $usersql): void {
|
||||
self::consume(';\s*', $tmpsql);
|
||||
if ($tmpsql) {
|
||||
throw new ValueException("unexpected value at end: $usersql");
|
||||
}
|
||||
}
|
||||
|
||||
function __construct($sql, ?array $params=null) {
|
||||
self::verifix($sql, $params);
|
||||
$this->sql = $sql;
|
||||
$this->params = $params;
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
protected $sql;
|
||||
|
||||
/** @var ?array */
|
||||
protected $params;
|
||||
|
||||
function useStmt(SQLite3 $db, ?SQLite3Stmt &$stmt=null, ?string &$sql=null): bool {
|
||||
if ($this->params !== null) {
|
||||
/** @var SQLite3Stmt $stmt */
|
||||
$stmt = SqliteException::check($db, $db->prepare($this->sql));
|
||||
$close = true;
|
||||
try {
|
||||
foreach ($this->params as $param => $value) {
|
||||
SqliteException::check($db, $stmt->bindValue($param, $value));
|
||||
}
|
||||
$close = false;
|
||||
return true;
|
||||
} finally {
|
||||
if ($close) $stmt->close();
|
||||
}
|
||||
} else {
|
||||
$sql = $this->sql;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
class _query_create extends _query {
|
||||
const SCHEMA = [
|
||||
"prefix" => "?string",
|
||||
"table" => "string",
|
||||
"schema" => "?array",
|
||||
"cols" => "?array",
|
||||
"suffix" => "?string",
|
||||
];
|
||||
|
||||
static function isa(string $sql): bool {
|
||||
//return preg_match("/^create(?:\s+table)?\b/i", $sql);
|
||||
#XXX implémentation minimale
|
||||
return preg_match("/^create\s+table\b/i", $sql);
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$params=null): string {
|
||||
#XXX implémentation minimale
|
||||
$sql = [self::merge_seq($query)];
|
||||
|
||||
## préfixe
|
||||
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
|
||||
|
||||
## table
|
||||
$sql[] = $query["table"];
|
||||
|
||||
## columns
|
||||
$cols = $query["cols"];
|
||||
$index = 0;
|
||||
foreach ($cols as $col => &$definition) {
|
||||
if ($col === $index) {
|
||||
$index++;
|
||||
} else {
|
||||
$definition = "$col $definition";
|
||||
}
|
||||
}; unset($definition);
|
||||
$sql[] = "(".implode(", ", $cols).")";
|
||||
|
||||
## suffixe
|
||||
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
|
||||
|
||||
## fin de la requête
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
class _query_delete extends _query {
|
||||
const SCHEMA = [
|
||||
"prefix" => "?string",
|
||||
"from" => "?string",
|
||||
"where" => "?array",
|
||||
"suffix" => "?string",
|
||||
];
|
||||
|
||||
static function isa(string $sql): bool {
|
||||
//return preg_match("/^delete(?:\s+from)?\b/i", $sql);
|
||||
#XXX implémentation minimale
|
||||
return preg_match("/^delete\s+from\b/i", $sql);
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$params=null): string {
|
||||
#XXX implémentation minimale
|
||||
$sql = [self::merge_seq($query)];
|
||||
|
||||
## préfixe
|
||||
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
|
||||
|
||||
## table
|
||||
$sql[] = $query["table"];
|
||||
|
||||
## where
|
||||
$where = $query["where"] ?? null;
|
||||
if ($where !== null) {
|
||||
_query::parse_conds($where, $wheresql, $params);
|
||||
if ($wheresql) {
|
||||
$sql[] = "where";
|
||||
$sql[] = implode(" and ", $wheresql);
|
||||
}
|
||||
}
|
||||
|
||||
## suffixe
|
||||
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
|
||||
|
||||
## fin de la requête
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\ValueException;
|
||||
|
||||
class _query_generic extends _query {
|
||||
static function isa(string $sql): bool {
|
||||
return preg_match('/^(?:drop\s+table)\b/i', $sql);
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$params=null): string {
|
||||
if (!cl::is_list($query)) {
|
||||
throw new ValueException("Seuls les tableaux séquentiels sont supportés");
|
||||
}
|
||||
return self::merge_seq($query);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\ValueException;
|
||||
|
||||
class _query_insert extends _query {
|
||||
const SCHEMA = [
|
||||
"prefix" => "?string",
|
||||
"into" => "?string",
|
||||
"schema" => "?array",
|
||||
"cols" => "?array",
|
||||
"values" => "?array",
|
||||
"suffix" => "?string",
|
||||
];
|
||||
|
||||
static function isa(string $sql): bool {
|
||||
return preg_match("/^insert\b/i", $sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* parser une chaine de la forme
|
||||
* "insert [into] [TABLE] [(COLS)] [values (VALUES)]"
|
||||
*/
|
||||
static function parse(array $query, ?array &$params=null): string {
|
||||
# fusionner d'abord toutes les parties séquentielles
|
||||
$usersql = $tmpsql = self::merge_seq($query);
|
||||
|
||||
### vérifier la présence des parties nécessaires
|
||||
$sql = [];
|
||||
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
|
||||
|
||||
## insert
|
||||
self::consume('insert\s*', $tmpsql);
|
||||
$sql[] = "insert";
|
||||
|
||||
## into
|
||||
self::consume('into\s*', $tmpsql);
|
||||
$sql[] = "into";
|
||||
$into = $query["into"] ?? null;
|
||||
if (self::consume('([a-z_][a-z0-9_]*)\s*', $tmpsql, $ms)) {
|
||||
if ($into === null) $into = $ms[1];
|
||||
$sql[] = $into;
|
||||
} elseif ($into !== null) {
|
||||
$sql[] = $into;
|
||||
} else {
|
||||
throw new ValueException("expected table name: $usersql");
|
||||
}
|
||||
|
||||
## cols & values
|
||||
$usercols = [];
|
||||
$uservalues = [];
|
||||
if (self::consume('\(([^)]*)\)\s*', $tmpsql, $ms)) {
|
||||
$usercols = array_merge($usercols, preg_split("/\s*,\s*/", $ms[1]));
|
||||
}
|
||||
$cols = cl::withn($query["cols"] ?? null);
|
||||
$values = cl::withn($query["values"] ?? null);
|
||||
$schema = $query["schema"] ?? null;
|
||||
if ($cols === null) {
|
||||
if ($usercols) {
|
||||
$cols = $usercols;
|
||||
} elseif ($values) {
|
||||
$cols = array_keys($values);
|
||||
$usercols = array_merge($usercols, $cols);
|
||||
} elseif ($schema && is_array($schema)) {
|
||||
#XXX implémenter support AssocSchema
|
||||
$cols = array_keys($schema);
|
||||
$usercols = array_merge($usercols, $cols);
|
||||
}
|
||||
}
|
||||
if (self::consume('values\s+\(\s*(.*)\s*\)\s*', $tmpsql, $ms)) {
|
||||
if ($ms[1]) $uservalues[] = $ms[1];
|
||||
}
|
||||
if ($cols !== null && !$uservalues) {
|
||||
if (!$usercols) $usercols = $cols;
|
||||
foreach ($cols as $col) {
|
||||
$uservalues[] = ":$col";
|
||||
$params[$col] = $values[$col] ?? null;
|
||||
}
|
||||
}
|
||||
$sql[] = "(" . implode(", ", $usercols) . ")";
|
||||
$sql[] = "values (" . implode(", ", $uservalues) . ")";
|
||||
|
||||
## suffixe
|
||||
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
|
||||
|
||||
## fin de la requête
|
||||
self::check_eof($tmpsql, $usersql);
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\ValueException;
|
||||
|
||||
class _query_select extends _query {
|
||||
const SCHEMA = [
|
||||
"prefix" => "?string",
|
||||
"schema" => "?array",
|
||||
"cols" => "?array",
|
||||
"from" => "?string",
|
||||
"where" => "?array",
|
||||
"order by" => "?array",
|
||||
"group by" => "?array",
|
||||
"having" => "?array",
|
||||
"suffix" => "?string",
|
||||
];
|
||||
|
||||
static function isa(string $sql): bool {
|
||||
return preg_match("/^select\b/i", $sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* parser une chaine de la forme
|
||||
* "select [COLS] [from TABLE] [where CONDS] [order by ORDERS] [group by GROUPS] [having CONDS]"
|
||||
*/
|
||||
static function parse(array $query, ?array &$params=null): string {
|
||||
# fusionner d'abord toutes les parties séquentielles
|
||||
$usersql = $tmpsql = self::merge_seq($query);
|
||||
|
||||
### vérifier la présence des parties nécessaires
|
||||
$sql = [];
|
||||
|
||||
## préfixe
|
||||
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
|
||||
|
||||
## select
|
||||
self::consume('select\s*', $tmpsql);
|
||||
$sql[] = "select";
|
||||
|
||||
## cols
|
||||
$usercols = [];
|
||||
if (self::consume('(.*?)\s*(?=$|\bfrom\b)', $tmpsql, $ms)) {
|
||||
if ($ms[1]) $usercols[] = $ms[1];
|
||||
}
|
||||
$tmpcols = cl::withn($query["cols"] ?? null);
|
||||
$schema = $query["schema"] ?? null;
|
||||
if ($tmpcols !== null) {
|
||||
$cols = [];
|
||||
$index = 0;
|
||||
foreach ($tmpcols as $key => $col) {
|
||||
if ($key === $index) {
|
||||
$index++;
|
||||
$cols[] = $col;
|
||||
$usercols[] = $col;
|
||||
} else {
|
||||
$cols[] = $key;
|
||||
$usercols[] = "$col as $key";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$cols = null;
|
||||
if ($schema && is_array($schema) && !in_array("*", $usercols)) {
|
||||
$cols = array_keys($schema);
|
||||
$usercols = array_merge($usercols, $cols);
|
||||
}
|
||||
}
|
||||
if (!$usercols && !$cols) $usercols = ["*"];
|
||||
$sql[] = implode(" ", $usercols);
|
||||
|
||||
## from
|
||||
$from = $query["from"] ?? null;
|
||||
if (self::consume('from\s+([a-z_][a-z0-9_]*)\s*(?=;?\s*$|\bwhere\b)', $tmpsql, $ms)) {
|
||||
if ($from === null) $from = $ms[1];
|
||||
$sql[] = "from";
|
||||
$sql[] = $from;
|
||||
} elseif ($from !== null) {
|
||||
$sql[] = "from";
|
||||
$sql[] = $from;
|
||||
} else {
|
||||
throw new ValueException("expected table name: $usersql");
|
||||
}
|
||||
|
||||
## where
|
||||
$userwhere = [];
|
||||
if (self::consume('where\b\s*(.*?)(?=;?\s*$|\border\s+by\b)', $tmpsql, $ms)) {
|
||||
if ($ms[1]) $userwhere[] = $ms[1];
|
||||
}
|
||||
$where = cl::withn($query["where"] ?? null);
|
||||
if ($where !== null) self::parse_conds($where, $userwhere, $params);
|
||||
if ($userwhere) {
|
||||
$sql[] = "where";
|
||||
$sql[] = implode(" and ", $userwhere);
|
||||
}
|
||||
|
||||
## order by
|
||||
$userorderby = [];
|
||||
if (self::consume('order\s+by\b\s*(.*?)(?=;?\s*$|\bgroup\s+by\b)', $tmpsql, $ms)) {
|
||||
if ($ms[1]) $userorderby[] = $ms[1];
|
||||
}
|
||||
$orderby = cl::withn($query["order by"] ?? null);
|
||||
if ($orderby !== null) {
|
||||
$index = 0;
|
||||
foreach ($orderby as $key => $value) {
|
||||
if ($key === $index) {
|
||||
$userorderby[] = $value;
|
||||
$index++;
|
||||
} else {
|
||||
if ($value === null) $value = false;
|
||||
if (!is_bool($value)) {
|
||||
$userorderby[] = "$key $value";
|
||||
} elseif ($value) {
|
||||
$userorderby[] = $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($userorderby) {
|
||||
$sql[] = "order by";
|
||||
$sql[] = implode(", ", $userorderby);
|
||||
}
|
||||
## group by
|
||||
$usergroupby = [];
|
||||
if (self::consume('group\s+by\b\s*(.*?)(?=;?\s*$|\bhaving\b)', $tmpsql, $ms)) {
|
||||
if ($ms[1]) $usergroupby[] = $ms[1];
|
||||
}
|
||||
$groupby = cl::withn($query["group by"] ?? null);
|
||||
if ($groupby !== null) {
|
||||
$index = 0;
|
||||
foreach ($groupby as $key => $value) {
|
||||
if ($key === $index) {
|
||||
$usergroupby[] = $value;
|
||||
$index++;
|
||||
} else {
|
||||
if ($value === null) $value = false;
|
||||
if (!is_bool($value)) {
|
||||
$usergroupby[] = "$key $value";
|
||||
} elseif ($value) {
|
||||
$usergroupby[] = $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($usergroupby) {
|
||||
$sql[] = "group by";
|
||||
$sql[] = implode(", ", $usergroupby);
|
||||
}
|
||||
|
||||
## having
|
||||
$userhaving = [];
|
||||
if (self::consume('having\b\s*(.*?)(?=;?\s*$)', $tmpsql, $ms)) {
|
||||
if ($ms[1]) $userhaving[] = $ms[1];
|
||||
}
|
||||
$having = cl::withn($query["having"] ?? null);
|
||||
if ($having !== null) self::parse_conds($having, $userhaving, $params);
|
||||
if ($userhaving) {
|
||||
$sql[] = "having";
|
||||
$sql[] = implode(" and ", $userhaving);
|
||||
}
|
||||
|
||||
## suffixe
|
||||
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
|
||||
|
||||
## fin de la requête
|
||||
self::check_eof($tmpsql, $usersql);
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
class _query_update extends _query {
|
||||
const SCHEMA = [
|
||||
"prefix" => "?string",
|
||||
"table" => "?string",
|
||||
"schema" => "?array",
|
||||
"cols" => "?array",
|
||||
"values" => "?array",
|
||||
"where" => "?array",
|
||||
"suffix" => "?string",
|
||||
];
|
||||
|
||||
static function isa(string $sql): bool {
|
||||
return preg_match("/^update\b/i", $sql);
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$params=null): string {
|
||||
#XXX implémentation minimale
|
||||
$sql = [self::merge_seq($query)];
|
||||
|
||||
## préfixe
|
||||
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
|
||||
|
||||
## table
|
||||
$sql[] = $query["table"];
|
||||
|
||||
## set
|
||||
_query::parse_set_values($query["values"], $setsql, $params);
|
||||
$sql[] = "set";
|
||||
$sql[] = implode(", ", $setsql);
|
||||
|
||||
## where
|
||||
$where = $query["where"] ?? null;
|
||||
if ($where !== null) {
|
||||
_query::parse_conds($where, $wheresql, $params);
|
||||
if ($wheresql) {
|
||||
$sql[] = "where";
|
||||
$sql[] = implode(" and ", $wheresql);
|
||||
}
|
||||
}
|
||||
|
||||
## suffixe
|
||||
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
|
||||
|
||||
## fin de la requête
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
<?php
|
||||
namespace nulib;
|
||||
|
||||
use nulib\file\base\FileReader;
|
||||
use nulib\file\base\FileWriter;
|
||||
use nulib\file\base\MemoryStream;
|
||||
use nulib\file\base\SharedFile;
|
||||
use nulib\file\base\TempStream;
|
||||
use nulib\file\base\TmpfileWriter;
|
||||
use nulib\file\FileReader;
|
||||
use nulib\file\FileWriter;
|
||||
use nulib\file\MemoryStream;
|
||||
use nulib\file\SharedFile;
|
||||
use nulib\file\TempStream;
|
||||
use nulib\file\TmpfileWriter;
|
||||
|
||||
/**
|
||||
* Class file: outils pour gérer les fichiers
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace nulib\file\base;
|
||||
namespace nulib\file;
|
||||
|
||||
/**
|
||||
* Class FileReader: un fichier accédé en lecture
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace nulib\file\base;
|
||||
namespace nulib\file;
|
||||
|
||||
use nulib\os\IOException;
|
||||
use nulib\os\sh;
|
|
@ -37,7 +37,7 @@ interface IReader extends _IFile {
|
|||
* verrouiller en mode partagé puis retourner un objet permettant de lire le
|
||||
* fichier.
|
||||
*/
|
||||
function getReader(bool $lockedByCanRead=false): IReader;
|
||||
function getReader(bool $alreadyLocked=false): IReader;
|
||||
|
||||
/**
|
||||
* lire tout le contenu du fichier en une seule fois, puis, si $close==true,
|
||||
|
@ -45,8 +45,8 @@ interface IReader extends _IFile {
|
|||
*
|
||||
* @throws IOException si une erreur se produit
|
||||
*/
|
||||
function getContents(bool $close=true, bool $lockedByCanRead=false): string;
|
||||
function getContents(bool $close=true, bool $alreadyLocked=false): string;
|
||||
|
||||
/** désérialiser le contenu du fichier, puis, si $close===true, le fermer */
|
||||
function unserialize(?array $options=null, bool $close=true, bool $lockedByCanRead=false);
|
||||
function unserialize(?array $options=null, bool $close=true, bool $alreadyLocked=false);
|
||||
}
|
||||
|
|
|
@ -30,11 +30,11 @@ interface IWriter extends _IFile {
|
|||
* verrouiller en mode exclusif puis retourner un objet permettant d'écrire
|
||||
* dans le fichier
|
||||
*/
|
||||
function getWriter(bool $lockedByCanWrite=false): IWriter;
|
||||
function getWriter(bool $alreadyLocked=false): IWriter;
|
||||
|
||||
/** écrire le contenu spécifié dans le fichier */
|
||||
function putContents(string $contents, bool $close=true, bool $lockedByCanWrite=false): void;
|
||||
function putContents(string $contents, bool $close=true, bool $alreadyLocked=false): void;
|
||||
|
||||
/** sérialiser l'objet dans la destination */
|
||||
function serialize($object, bool $close=true, bool $lockedByCanWrite=false): void;
|
||||
function serialize($object, bool $close=true, bool $alreadyLocked=false): void;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace nulib\file\base;
|
||||
namespace nulib\file;
|
||||
|
||||
/**
|
||||
* Class MemoryStream: un flux qui peut être lu ou écrit, et qui reste
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace nulib\file\base;
|
||||
namespace nulib\file;
|
||||
|
||||
use nulib\ValueException;
|
||||
|
|
@ -1,14 +1,12 @@
|
|||
<?php
|
||||
namespace nulib\file\base;
|
||||
namespace nulib\file;
|
||||
|
||||
use nulib\file\csv\csv_flavours;
|
||||
use nulib\file\IReader;
|
||||
use nulib\file\IWriter;
|
||||
use nulib\NoMoreDataException;
|
||||
use nulib\os\EOFException;
|
||||
use nulib\os\IOException;
|
||||
use nulib\php\iter\AbstractIterator;
|
||||
use nulib\ref\os\csv\ref_csv;
|
||||
use nulib\ref\file\csv\ref_csv;
|
||||
use nulib\str;
|
||||
use nulib\ValueException;
|
||||
|
||||
|
@ -234,6 +232,14 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
|||
return iterator_to_array($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* verrouiller le fichier en lecture de façon inconditionelle (ignorer la
|
||||
* valeur de $useLocking). bloquer jusqu'à ce que le verrou soit disponible
|
||||
*/
|
||||
function lockRead(): void {
|
||||
$this->lock(LOCK_SH);
|
||||
}
|
||||
|
||||
/**
|
||||
* essayer de verrouiller le fichier en lecture. retourner true si l'opération
|
||||
* réussit. dans ce cas, il faut appeler {@link getReader()} avec l'argument
|
||||
|
@ -248,8 +254,8 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
|||
* verrouiller en mode partagé puis retourner un objet permettant de lire le
|
||||
* fichier.
|
||||
*/
|
||||
function getReader(bool $lockedByCanRead=false): IReader {
|
||||
if ($this->useLocking && !$lockedByCanRead) $this->lock(LOCK_SH);
|
||||
function getReader(bool $alreadyLocked=false): IReader {
|
||||
if ($this->useLocking && !$alreadyLocked) $this->lock(LOCK_SH);
|
||||
return new class($this->fd, ++$this->serial, $this) extends Stream {
|
||||
function __construct($fd, int $serial, Stream $parent) {
|
||||
$this->parent = $parent;
|
||||
|
@ -270,9 +276,9 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
|||
}
|
||||
|
||||
/** retourner le contenu du fichier sous forme de chaine */
|
||||
function getContents(bool $close=true, bool $lockedByCanRead=false): string {
|
||||
function getContents(bool $close=true, bool $alreadyLocked=false): string {
|
||||
$useLocking = $this->useLocking;
|
||||
if ($useLocking && !$lockedByCanRead) $this->lock(LOCK_SH);
|
||||
if ($useLocking && !$alreadyLocked) $this->lock(LOCK_SH);
|
||||
try {
|
||||
return IOException::ensure_valid(stream_get_contents($this->fd), $this->throwOnError);
|
||||
} finally {
|
||||
|
@ -281,8 +287,8 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
|||
}
|
||||
}
|
||||
|
||||
function unserialize(?array $options=null, bool $close=true, bool $lockedByCanRead=false) {
|
||||
$args = [$this->getContents($lockedByCanRead)];
|
||||
function unserialize(?array $options=null, bool $close=true, bool $alreadyLocked=false) {
|
||||
$args = [$this->getContents($close, $alreadyLocked)];
|
||||
if ($options !== null) $args[] = $options;
|
||||
return unserialize(...$args);
|
||||
}
|
||||
|
@ -331,9 +337,10 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
|||
}
|
||||
|
||||
/** @throws IOException */
|
||||
function ftruncate(int $size): self {
|
||||
function ftruncate(int $size=0, bool $rewind=true): self {
|
||||
$fd = $this->getResource();
|
||||
IOException::ensure_valid(ftruncate($fd, $size), $this->throwOnError);
|
||||
if ($rewind) rewind($fd);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -347,6 +354,14 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* verrouiller le fichier en écriture de façon inconditionelle (ignorer la
|
||||
* valeur de $useLocking). bloquer jusqu'à ce que le verrou soit disponible
|
||||
*/
|
||||
function lockWrite(): void {
|
||||
$this->lock(LOCK_EX);
|
||||
}
|
||||
|
||||
/**
|
||||
* essayer de verrouiller le fichier en écriture. retourner true si l'opération
|
||||
* réussit. dans ce cas, il faut appeler {@link getWriter()} avec l'argument
|
||||
|
@ -361,8 +376,8 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
|||
* verrouiller en mode exclusif puis retourner un objet permettant d'écrire
|
||||
* dans le fichier
|
||||
*/
|
||||
function getWriter(bool $lockedByCanWrite=false): IWriter {
|
||||
if ($this->useLocking && !$lockedByCanWrite) $this->lock(LOCK_EX);
|
||||
function getWriter(bool $alreadyLocked=false): IWriter {
|
||||
if ($this->useLocking && !$alreadyLocked) $this->lock(LOCK_EX);
|
||||
return new class($this->fd, ++$this->serial, $this) extends Stream {
|
||||
function __construct($fd, int $serial, Stream $parent) {
|
||||
$this->parent = $parent;
|
||||
|
@ -383,9 +398,9 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
|||
};
|
||||
}
|
||||
|
||||
function putContents(string $contents, bool $close=true, bool $lockedByCanWrite=false): void {
|
||||
function putContents(string $contents, bool $close=true, bool $alreadyLocked=false): void {
|
||||
$useLocking = $this->useLocking;
|
||||
if ($useLocking && !$lockedByCanWrite) $this->lock(LOCK_EX);
|
||||
if ($useLocking && !$alreadyLocked) $this->lock(LOCK_EX);
|
||||
try {
|
||||
$this->fwrite($contents);
|
||||
} finally {
|
||||
|
@ -394,7 +409,7 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
|||
}
|
||||
}
|
||||
|
||||
function serialize($object, bool $close=true, bool $lockedByCanWrite=false): void {
|
||||
$this->putContents(serialize($object), $lockedByCanWrite);
|
||||
function serialize($object, bool $close=true, bool $alreadyLocked=false): void {
|
||||
$this->putContents(serialize($object), $close, $alreadyLocked);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
namespace nulib\file\base;
|
||||
namespace nulib\file;
|
||||
|
||||
use nulib\file\_IFile;
|
||||
use nulib\os\IOException;
|
||||
|
||||
trait TStreamFilter {
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace nulib\file\base;
|
||||
namespace nulib\file;
|
||||
|
||||
/**
|
||||
* Class TempStream: un flux qui peut être lu ou écrit, et qui reste en mémoire,
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace nulib\file\base;
|
||||
namespace nulib\file;
|
||||
|
||||
use nulib\os\IOException;
|
||||
use nulib\os\path;
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace nulib\file\base;
|
||||
namespace nulib\file;
|
||||
|
||||
use nulib\os\IOException;
|
||||
use nulib\web\http;
|
||||
|
@ -12,9 +12,17 @@ abstract class _File extends Stream {
|
|||
/** @var string */
|
||||
protected $file;
|
||||
|
||||
function getFile(): ?string {
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
protected $mode;
|
||||
|
||||
function getMode(): string {
|
||||
return $this->mode;
|
||||
}
|
||||
|
||||
/** @return resource */
|
||||
protected function open() {
|
||||
return IOException::ensure_valid(@fopen($this->file, $this->mode));
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
namespace nulib\file\app;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\file\SharedFile;
|
||||
use nulib\output\msg;
|
||||
use nulib\php\time\DateTime;
|
||||
|
||||
/**
|
||||
* Class LockFile: une classe qui permet à une application de verrouiller
|
||||
* certaines actions
|
||||
*/
|
||||
class LockFile {
|
||||
const NAME = null;
|
||||
|
||||
const TITLE = null;
|
||||
|
||||
function __construct($file, ?string $name=null, ?string $title=null) {
|
||||
$this->file = new SharedFile($file);
|
||||
$this->name = $name ?? static::NAME;
|
||||
$this->title = $title ?? static::TITLE;
|
||||
}
|
||||
|
||||
/** @var SharedFile */
|
||||
protected $file;
|
||||
|
||||
/** @var ?string */
|
||||
protected $name;
|
||||
|
||||
/** @var ?string */
|
||||
protected $title;
|
||||
|
||||
protected function initData(): array {
|
||||
return [
|
||||
"name" => $this->name,
|
||||
"title" => $this->title,
|
||||
"locked" => false,
|
||||
"date_lock" => null,
|
||||
"date_release" => null,
|
||||
];
|
||||
}
|
||||
|
||||
function read(bool $close=true): array {
|
||||
$data = $this->file->unserialize(null, $close);
|
||||
if (!is_array($data)) $data = $this->initData();
|
||||
return $data;
|
||||
}
|
||||
|
||||
function isLocked(?array &$data=null): bool {
|
||||
$data = $this->read();
|
||||
return $data["locked"];
|
||||
}
|
||||
|
||||
function warnIfLocked(?array $data=null): void {
|
||||
if ($data === null) $data = $this->read();
|
||||
if ($data["locked"]) {
|
||||
msg::warning("$data[name]: possède le verrou depuis $data[date_lock] -- $data[title]");
|
||||
}
|
||||
}
|
||||
|
||||
function lock(?array &$data=null): bool {
|
||||
$file = $this->file;
|
||||
$data = $this->read(false);
|
||||
if ($data["locked"]) {
|
||||
$file->close();
|
||||
return false;
|
||||
} else {
|
||||
$file->ftruncate();
|
||||
$file->serialize(cl::merge($data, [
|
||||
"locked" => true,
|
||||
"date_lock" => new DateTime(),
|
||||
"date_release" => null,
|
||||
]));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function release(?array &$data=null): void {
|
||||
$file = $this->file;
|
||||
$data = $this->read(false);
|
||||
$file->ftruncate();
|
||||
$file->serialize(cl::merge($data, [
|
||||
"locked" => false,
|
||||
"date_release" => new DateTime(),
|
||||
]));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
namespace nulib\file\app;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\file\SharedFile;
|
||||
use nulib\os\path;
|
||||
use nulib\php\time\DateTime;
|
||||
use nulib\str;
|
||||
|
||||
/**
|
||||
* Class RunFile: une classe permettant de suivre le fonctionnement d'une
|
||||
* application qui tourne en tâche de fond
|
||||
*/
|
||||
class RunFile {
|
||||
const RUN_EXT = ".run";
|
||||
const LOCK_EXT = ".lock";
|
||||
|
||||
const NAME = null;
|
||||
|
||||
function __construct(string $file, ?string $name=null) {
|
||||
$file = path::ensure_ext($file, self::RUN_EXT);
|
||||
$this->file = new SharedFile($file);
|
||||
$this->name = $name ?? static::NAME;
|
||||
}
|
||||
|
||||
/** @var SharedFile */
|
||||
protected $file;
|
||||
|
||||
/** @var ?string */
|
||||
protected $name;
|
||||
|
||||
protected static function merge(array $data, array $merge): array {
|
||||
return cl::merge($data, [
|
||||
"serial" => $data["serial"] + 1,
|
||||
], $merge);
|
||||
}
|
||||
|
||||
protected function initData(bool $withDateStart=true): array {
|
||||
$dateStart = $withDateStart? new DateTime(): null;
|
||||
return [
|
||||
"name" => $this->name,
|
||||
"serial" => 0,
|
||||
"date_start" => $dateStart,
|
||||
"date_stop" => null,
|
||||
"action" => null,
|
||||
"action_date_start" => null,
|
||||
"action_max_step" => null,
|
||||
"action_current_step" => null,
|
||||
"action_date_step" => null,
|
||||
];
|
||||
}
|
||||
|
||||
function read(): array {
|
||||
$data = $this->file->unserialize();
|
||||
if (!is_array($data)) $data = $this->initData(false);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/** tester si l'application est démarrée */
|
||||
function isStarted(): bool {
|
||||
$data = $this->read();
|
||||
return $data["date_start"] !== null;
|
||||
}
|
||||
|
||||
/** tester si l'application est arrêtée */
|
||||
function isStopped(): bool {
|
||||
$data = $this->read();
|
||||
return $data["date_stop"] !== null;
|
||||
}
|
||||
|
||||
function haveWorked(int $serial, ?int &$currentSerial=null): bool {
|
||||
$data = $this->read();
|
||||
return $serial !== $data["serial"];
|
||||
}
|
||||
|
||||
protected function willWrite(): array {
|
||||
$file = $this->file;
|
||||
$file->lockWrite();
|
||||
$data = $file->unserialize(null, false, true);
|
||||
if (!is_array($data)) {
|
||||
$data = $this->initData();
|
||||
$file->ftruncate();
|
||||
$file->serialize($data, false, true);
|
||||
}
|
||||
$file->ftruncate();
|
||||
return [$file, $data];
|
||||
}
|
||||
|
||||
/** indiquer que l'application démarre */
|
||||
function start(): void {
|
||||
$this->file->serialize($this->initData());
|
||||
}
|
||||
|
||||
/** indiquer le début d'une action */
|
||||
function action(?string $title, ?int $maxSteps=null): void {
|
||||
[$file, $data] = $this->willWrite();
|
||||
$file->serialize(self::merge($data, [
|
||||
"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 {
|
||||
[$file, $data] = $this->willWrite();
|
||||
$file->serialize(self::merge($data, [
|
||||
"action_date_step" => new DateTime(),
|
||||
"action_current_step" => $data["action_current_step"] + $nbSteps,
|
||||
]));
|
||||
}
|
||||
|
||||
/** indiquer que l'application s'arrête */
|
||||
function stop(): void {
|
||||
[$file, $data] = $this->willWrite();
|
||||
$file->serialize(self::merge($data, [
|
||||
"date_stop" => new DateTime(),
|
||||
]));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
namespace nulib\file\csv;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\ref\os\csv\ref_csv;
|
||||
use nulib\ref\file\csv\ref_csv;
|
||||
|
||||
class csv_flavours {
|
||||
const MAP = [
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
namespace nulib\file\web;
|
||||
|
||||
use nulib\file\FileReader;
|
||||
use nulib\php\coll\BaseArray;
|
||||
use nulib\ValueException;
|
||||
|
||||
/**
|
||||
* Class Upload: un fichier téléversé
|
||||
*
|
||||
* @property-read string $name
|
||||
* @property-read string $type
|
||||
* @property-read string $tmpName
|
||||
* @property-read int $error
|
||||
* @property-read int $size
|
||||
* @property-read string $fullPath
|
||||
*/
|
||||
class Upload extends BaseArray {
|
||||
const MESSAGES = [
|
||||
"invalid" => "Ceci n'est pas un fichier téléversé",
|
||||
"nofile" => "Aucun fichier n'a été fourni",
|
||||
"toobig" => "Le fichier que vous avez fourni est trop volumineux.",
|
||||
"unknown" => "Une erreur s'est produite pendant le transfert du fichier. Veuillez réessayer.",
|
||||
];
|
||||
|
||||
protected static function error(string $message) {
|
||||
return new ValueException(static::MESSAGES[$message]);
|
||||
}
|
||||
|
||||
function __construct(?array $file, bool $required=true, bool $check=true) {
|
||||
parent::__construct($file);
|
||||
if ($check) $this->check($required);
|
||||
}
|
||||
|
||||
function check(bool $required=true, bool $throw=true): bool {
|
||||
$file = $this->data;
|
||||
if ($file) {
|
||||
$name = $file["name"] ?? null;
|
||||
$type = $file["type"] ?? null;
|
||||
$error = $file["error"] ?? null;
|
||||
if (!is_scalar($name) || !is_scalar($type) || !is_scalar($error)) {
|
||||
if ($throw) throw static::error("invalid");
|
||||
else return false;
|
||||
}
|
||||
switch ($error) {
|
||||
case UPLOAD_ERR_OK:
|
||||
break;
|
||||
case UPLOAD_ERR_NO_FILE:
|
||||
if ($required) {
|
||||
if ($throw) throw self::error("nofile");
|
||||
else return false;
|
||||
}
|
||||
break;
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
if ($throw) throw self::error("toobig");
|
||||
else return false;
|
||||
default:
|
||||
if ($throw) self::error("unknown");
|
||||
else return false;
|
||||
}
|
||||
} elseif ($required) {
|
||||
if ($throw) throw static::error("nofile");
|
||||
else return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const _AUTO_PROPERTIES = [
|
||||
"tmpName" => "tmp_name",
|
||||
"fullPath" => "full_path",
|
||||
];
|
||||
function &__get($name) {
|
||||
$name = static::_AUTO_PROPERTIES[$name] ?? $name;
|
||||
return parent::__get($name);
|
||||
}
|
||||
|
||||
function isError(): bool {
|
||||
$error = $this->error;
|
||||
return $error !== UPLOAD_ERR_OK && $error !== UPLOAD_ERR_NO_FILE;
|
||||
}
|
||||
|
||||
function isValid(): bool {
|
||||
return $this->error === UPLOAD_ERR_OK;
|
||||
}
|
||||
|
||||
/** @var ?string chemin du fichier, s'il a été déplacé */
|
||||
protected $file;
|
||||
|
||||
function moveTo(string $dest): bool {
|
||||
if ($this->file === null) {
|
||||
$moved = move_uploaded_file($this->tmpName, $dest);
|
||||
if ($moved) $this->file = $dest;
|
||||
} else {
|
||||
$moved = false;
|
||||
}
|
||||
return $moved;
|
||||
}
|
||||
|
||||
function getFile(): FileReader {
|
||||
$file = $this->file ?? $this->tmpName;
|
||||
return new FileReader($file, "r+b");
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
<?php
|
||||
namespace nulib\output;
|
||||
|
||||
/**
|
||||
* Interface IContent: un objet capable de produire du contenu à afficher
|
||||
*/
|
||||
interface IContent {
|
||||
/** retourner le contenu à afficher */
|
||||
function getContent(): iterable;
|
||||
}
|
|
@ -5,7 +5,7 @@ namespace nulib\output;
|
|||
* Interface IMessenger: un objet pouvant afficher des messages de l'application
|
||||
*/
|
||||
interface IMessenger {
|
||||
const DEBUG = -1, MINOR = 0, NORMAL = 1, MAJOR = 2, NONE = 3;
|
||||
const DEBUG = 0, MINOR = 1, NORMAL = 2, MAJOR = 3, NONE = 4;
|
||||
const MIN_LEVEL = self::DEBUG, MAX_LEVEL = self::MAJOR;
|
||||
|
||||
/** réinitialiser les paramètres de l'objet */
|
||||
|
@ -54,21 +54,32 @@ interface IMessenger {
|
|||
*
|
||||
* démarrer une action le cas échéant (et la terminer aussitôt)
|
||||
*/
|
||||
function asuccess($content=null): void;
|
||||
function asuccess($content=null, ?int $overrideLevel=null): void;
|
||||
|
||||
/**
|
||||
* terminer l'action courante avec le résultat "échec"
|
||||
*
|
||||
* démarrer une action le cas échéant (et la terminer aussitôt)
|
||||
*/
|
||||
function afailure($content=null): void;
|
||||
function afailure($content=null, ?int $overrideLevel=null): void;
|
||||
|
||||
/**
|
||||
* terminer l'action courante avec le résultat "neutre"
|
||||
*
|
||||
* démarrer une action le cas échéant (et la terminer aussitôt)
|
||||
*/
|
||||
function adone($content=null): void;
|
||||
function adone($content=null, ?int $overrideLevel=null): void;
|
||||
|
||||
/**
|
||||
* terminer l'action courante avec le résultat "succès", "échec" ou "neutre"
|
||||
* en fonction de la valeur de $result
|
||||
* - si c'est un booléen, true vaut succès, false vaut échec
|
||||
* - si c'est une exception, c'est un échec et le message est affiché
|
||||
* - sinon, le résultat est neutre et le message est affiché s'il n'est pas null
|
||||
*
|
||||
* démarrer une action le cas échéant (et la terminer aussitôt)
|
||||
*/
|
||||
function aresult($result=null, ?int $overrideLevel=null): void;
|
||||
|
||||
/** afficher une donnée non structurée */
|
||||
function print($content, ?int $level=null): void;
|
||||
|
@ -80,7 +91,7 @@ interface IMessenger {
|
|||
function note($content, ?int $level=null): void;
|
||||
|
||||
/** afficher un événément "avertissement" */
|
||||
function warn($content, ?int $level=null): void;
|
||||
function warning($content, ?int $level=null): void;
|
||||
|
||||
/** afficher un événément "erreur" */
|
||||
function error($content, ?int $level=null): void;
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
<?php
|
||||
namespace nulib\output;
|
||||
|
||||
/**
|
||||
* Interface IPrintable: un objet qui peut écrire du contenu sur la sortie
|
||||
* standard
|
||||
*/
|
||||
interface IPrintable {
|
||||
/** afficher le contenu */
|
||||
function print(): void;
|
||||
}
|
|
@ -4,8 +4,28 @@
|
|||
rotation des logs
|
||||
* [ ] lors de la rotation, si l'ouverture du nouveau fichier échoue, continuer
|
||||
à écrire dans l'ancien fichier
|
||||
* ou alors un moyen pour ré-ouvrir la sortie, afin de pouvoir indiquer à un
|
||||
long running process qu'une rotation a eu lieu
|
||||
* [ ] dans `StdMessenger::resetParams()`, `[output]` peut être une instance de
|
||||
StdOutput pour mettre à jour $out ET $err, ou un tableau de deux éléments pour
|
||||
mettre à jour séparément $out et $err
|
||||
* [ ] vérifier que la date affichée pour un TITLE est celle à laquelle l'appel
|
||||
a été fait, même si le premier événement en dessous arrive bien plus tard
|
||||
* [ ] pareil pour action: sauf si c'est une seule ligne, la date de action est
|
||||
la date du premier appel, alors que la date de $result est celui du result si
|
||||
c'est affiché sur une autre ligne
|
||||
* réorganiser pour que msg:: attaque un proxy dans lequel est configuré un
|
||||
ensemble standard de sorties: say, log, debuglog
|
||||
* `--aD, --av, --aq, --asilent` pour les valeurs d'ajustement qui sont un
|
||||
incrément à la valeur courante (+2, +1, -1, -2)
|
||||
* `--yD, --yv, --yq, --ysilent, -D, -v, -q, --silent` pour say
|
||||
* `--lD, --lv, --lq, --lsilent` pour log, `-L:, --L` l'active
|
||||
* `--DD, --Dv, --Dq, --Dsilent` pour debuglog, `--DL:` l'active
|
||||
|
||||
question à régler: trouver un moyen pour que l'affichage web soit "un niveau au
|
||||
dessus" afin de ne pas se retrouver avec dans les logs des messages uniquement
|
||||
pour l'UI
|
||||
peut-être rajouter `ui` (ou `web`?) en plus de say, log, debuglog?
|
||||
--> ou renommer `say` en `console`, et `ui` en `say`
|
||||
|
||||
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
|
|
@ -2,7 +2,6 @@
|
|||
namespace nulib\output;
|
||||
|
||||
use nulib\str;
|
||||
use nulib\ValueException;
|
||||
|
||||
/**
|
||||
* Class _messenger: classe de base pour say, log et msg
|
||||
|
@ -59,13 +58,14 @@ abstract class _messenger {
|
|||
static function desc($content, ?int $level=null): void { static::get()->desc($content, $level); }
|
||||
static function action($content, ?callable $func=null, ?int $level=null): void { static::get()->action($content, $func, $level); }
|
||||
static function step($content, ?int $level=null): void { static::get()->step($content, $level); }
|
||||
static function asuccess($content=null): void { static::get()->asuccess($content); }
|
||||
static function afailure($content=null): void { static::get()->afailure($content); }
|
||||
static function adone($content=null): void { static::get()->adone($content); }
|
||||
static function asuccess($content=null, ?int $override_level=null): void { static::get()->asuccess($content, $override_level); }
|
||||
static function afailure($content=null, ?int $override_level=null): void { static::get()->afailure($content, $override_level); }
|
||||
static function adone($content=null, ?int $override_level=null): void { static::get()->adone($content, $override_level); }
|
||||
static function aresult($result=null, ?int $override_level=null): void { static::get()->aresult($result, $override_level); }
|
||||
static function print($content, ?int $level=null): void { static::get()->print($content, $level); }
|
||||
static function info($content, ?int $level=null): void { static::get()->info($content, $level); }
|
||||
static function note($content, ?int $level=null): void { static::get()->note($content, $level); }
|
||||
static function warn($content, ?int $level=null): void { static::get()->warn($content, $level); }
|
||||
static function warning($content, ?int $level=null): void { static::get()->warning($content, $level); }
|
||||
static function error($content, ?int $level=null): void { static::get()->error($content, $level); }
|
||||
static function end(bool $all=false): void { static::get()->end($all); }
|
||||
|
||||
|
@ -74,6 +74,6 @@ abstract class _messenger {
|
|||
static function minor($content): void { self::info($content, self::MINOR);}
|
||||
static function important($content): void { self::info($content, self::MAJOR);}
|
||||
static function attention($content): void { self::note($content, self::MAJOR);}
|
||||
static function critwarn($content): void { self::warn($content, self::MAJOR);}
|
||||
static function critwarning($content): void { self::warning($content, self::MAJOR);}
|
||||
static function criterror($content): void { self::error($content, self::MAJOR);}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<?php
|
||||
namespace nulib\output;
|
||||
|
||||
use nulib\ValueException;
|
||||
use nulib\cl;
|
||||
use nulib\output\std\ProxyMessenger;
|
||||
use nulib\ValueException;
|
||||
|
||||
/**
|
||||
* Class log: inscrire un message dans les logs uniquement
|
||||
|
@ -13,13 +14,15 @@ use nulib\output\std\ProxyMessenger;
|
|||
class log extends _messenger {
|
||||
static function set_messenger(IMessenger $log=null) {
|
||||
self::$log = $log;
|
||||
// forcer la recréation de l'instance partagée $msg
|
||||
self::$msg = null;
|
||||
}
|
||||
|
||||
static function set_messenger_class(string $log_class=null, ?array $params=null) {
|
||||
if (!is_subclass_of($log_class, IMessenger::class)) {
|
||||
throw ValueException::invalid_class($log_class, IMessenger::class);
|
||||
}
|
||||
self::$log = new $log_class($params);
|
||||
self::set_messenger(new $log_class($params));
|
||||
}
|
||||
|
||||
static function get(): IMessenger {
|
||||
|
@ -30,4 +33,17 @@ class log extends _messenger {
|
|||
}
|
||||
return self::$msg;
|
||||
}
|
||||
|
||||
static function have_log(): bool {
|
||||
return self::$log !== null;
|
||||
}
|
||||
|
||||
static function create_or_reset_params(?array $params=null, string $log_class=null, ?array $create_params=null) {
|
||||
if (self::$log === null) {
|
||||
$params = cl::merge($params, $create_params);
|
||||
self::set_messenger_class($log_class, $params);
|
||||
} else {
|
||||
self::reset_params($params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
namespace nulib\output;
|
||||
|
||||
use nulib\ValueException;
|
||||
use nulib\output\std\ProxyMessenger;
|
||||
use nulib\ValueException;
|
||||
|
||||
/**
|
||||
* Class msg: inscrire un message dans les logs ET l'afficher sur la console
|
||||
|
@ -12,22 +12,32 @@ use nulib\output\std\ProxyMessenger;
|
|||
* les classes {@link say} et {@link log} sont utilisables aussi
|
||||
*/
|
||||
class msg extends _messenger {
|
||||
static function set_messenger(IMessenger $say, ?IMessenger $log=null) {
|
||||
self::$say = $say;
|
||||
static function set_messenger(?IMessenger $say, ?IMessenger $log=null) {
|
||||
if ($say !== null) self::$say = $say;
|
||||
if ($log !== null) self::$log = $log;
|
||||
if ($say !== null || $log !== null) {
|
||||
// forcer la recréation de l'instance partagée $msg
|
||||
self::$msg = null;
|
||||
}
|
||||
}
|
||||
|
||||
static function set_messenger_class(string $say_class, ?string $log_class=null) {
|
||||
if (!is_subclass_of($say_class, IMessenger::class)) {
|
||||
throw ValueException::invalid_class($say_class, IMessenger::class);
|
||||
static function set_messenger_class(?string $say_class, ?string $log_class=null) {
|
||||
if ($say_class !== null) {
|
||||
if (!is_subclass_of($say_class, IMessenger::class)) {
|
||||
throw ValueException::invalid_class($say_class, IMessenger::class);
|
||||
}
|
||||
self::$say = new $say_class();
|
||||
}
|
||||
self::$say = new $say_class();
|
||||
if ($log_class !== null) {
|
||||
if (!is_subclass_of($log_class, IMessenger::class)) {
|
||||
throw ValueException::invalid_class($log_class, IMessenger::class);
|
||||
}
|
||||
self::$log = new $log_class();
|
||||
}
|
||||
if ($say_class !== null || $log_class !== null) {
|
||||
// forcer la recréation de l'instance partagée $msg
|
||||
self::$msg = null;
|
||||
}
|
||||
}
|
||||
|
||||
static function get(): IMessenger {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
namespace nulib\output;
|
||||
|
||||
use nulib\ValueException;
|
||||
use nulib\output\std\ProxyMessenger;
|
||||
use nulib\ValueException;
|
||||
|
||||
/**
|
||||
* Class say: afficher un message sur la console uniquement
|
||||
|
@ -13,13 +13,15 @@ use nulib\output\std\ProxyMessenger;
|
|||
class say extends _messenger {
|
||||
static function set_messenger(IMessenger $say) {
|
||||
self::$say = $say;
|
||||
// forcer la recréation de l'instance partagée $msg
|
||||
self::$msg = null;
|
||||
}
|
||||
|
||||
static function set_messenger_class(string $say_class, ?array $params=null) {
|
||||
if (!is_subclass_of($say_class, IMessenger::class)) {
|
||||
throw ValueException::invalid_class($say_class, IMessenger::class);
|
||||
}
|
||||
self::$say = new $say_class($params);
|
||||
self::set_messenger(new $say_class($params));
|
||||
}
|
||||
|
||||
static function get(): IMessenger {
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
<?php
|
||||
namespace nulib\output\std;
|
||||
|
||||
use Exception;
|
||||
use nulib\output\IMessenger;
|
||||
|
||||
/**
|
||||
* Class ProxyMessenger: un proxy vers ou un plusieurs instances de IMessenger
|
||||
*
|
||||
* NB: si cette classe est instanciée sans argument, elle agit comme un
|
||||
* "NullMessenger", c'est à dire une instance qui envoie tous les messages vers
|
||||
* /dev/null
|
||||
*/
|
||||
class ProxyMessenger implements IMessenger {
|
||||
function __construct(?IMessenger ...$msgs) {
|
||||
|
@ -46,11 +51,11 @@ class ProxyMessenger implements IMessenger {
|
|||
$useFunc = false;
|
||||
$untils = [];
|
||||
foreach ($this->msgs as $msg) {
|
||||
$msg->title($content, null, $level);
|
||||
if ($msg instanceof _IMessenger) {
|
||||
$useFunc = true;
|
||||
$untils[] = $msg->_getTitleMark();
|
||||
}
|
||||
$msg->title($content, null, $level);
|
||||
}
|
||||
if ($useFunc && $func !== null) {
|
||||
try {
|
||||
|
@ -59,7 +64,9 @@ class ProxyMessenger implements IMessenger {
|
|||
/** @var _IMessenger $msg */
|
||||
$index = 0;
|
||||
foreach ($this->msgs as $msg) {
|
||||
$msg->_endTitle($untils[$index++]);
|
||||
if ($msg instanceof _IMessenger) {
|
||||
$msg->_endTitle($untils[$index++]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,12 +79,25 @@ class ProxyMessenger implements IMessenger {
|
|||
$msg->action($content, null, $level);
|
||||
if ($msg instanceof _IMessenger) {
|
||||
$useFunc = true;
|
||||
$untils[] = $msg->_getTitleMark();
|
||||
$untils[] = $msg->_getActionMark();
|
||||
}
|
||||
}
|
||||
if ($useFunc && $func !== null) {
|
||||
try {
|
||||
$func($this);
|
||||
$result = $func($this);
|
||||
/** @var _IMessenger $msg */
|
||||
$index = 0;
|
||||
foreach ($this->msgs as $msg) {
|
||||
if ($msg->_getActionMark() > $untils[$index++]) {
|
||||
$msg->aresult($result);
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
/** @var _IMessenger $msg */
|
||||
foreach ($this->msgs as $msg) {
|
||||
$msg->afailure($e);
|
||||
}
|
||||
throw $e;
|
||||
} finally {
|
||||
/** @var _IMessenger $msg */
|
||||
$index = 0;
|
||||
|
@ -88,13 +108,14 @@ class ProxyMessenger implements IMessenger {
|
|||
}
|
||||
}
|
||||
function step($content, ?int $level=null): void { foreach ($this->msgs as $msg) { $msg->step($content, $level); } }
|
||||
function asuccess($content=null): void { foreach ($this->msgs as $msg) { $msg->asuccess($content); } }
|
||||
function afailure($content=null): void { foreach ($this->msgs as $msg) { $msg->afailure($content); } }
|
||||
function adone($content=null): void { foreach ($this->msgs as $msg) { $msg->adone($content); } }
|
||||
function asuccess($content=null, ?int $overrideLevel=null): void { foreach ($this->msgs as $msg) { $msg->asuccess($content, $overrideLevel); } }
|
||||
function afailure($content=null, ?int $overrideLevel=null): void { foreach ($this->msgs as $msg) { $msg->afailure($content, $overrideLevel); } }
|
||||
function adone($content=null, ?int $overrideLevel=null): void { foreach ($this->msgs as $msg) { $msg->adone($content, $overrideLevel); } }
|
||||
function aresult($result=null, ?int $overrideLevel=null): void { foreach ($this->msgs as $msg) { $msg->aresult($result, $overrideLevel); } }
|
||||
function print($content, ?int $level=null): void { foreach ($this->msgs as $msg) { $msg->print($content, $level); } }
|
||||
function info($content, ?int $level=null): void { foreach ($this->msgs as $msg) { $msg->info($content, $level); } }
|
||||
function note($content, ?int $level=null): void { foreach ($this->msgs as $msg) { $msg->note($content, $level); } }
|
||||
function warn($content, ?int $level=null): void { foreach ($this->msgs as $msg) { $msg->warn($content, $level); } }
|
||||
function warning($content, ?int $level=null): void { foreach ($this->msgs as $msg) { $msg->warning($content, $level); } }
|
||||
function error($content, ?int $level=null): void { foreach ($this->msgs as $msg) { $msg->error($content, $level); } }
|
||||
function end(bool $all=false): void { foreach ($this->msgs as $msg) { $msg->end($all); } }
|
||||
}
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
namespace nulib\output\std;
|
||||
|
||||
use Exception;
|
||||
use nulib\A;
|
||||
use nulib\cl;
|
||||
use nulib\ExceptionShadow;
|
||||
use nulib\UserException;
|
||||
use nulib\output\IMessenger;
|
||||
use nulib\UserException;
|
||||
use Throwable;
|
||||
|
||||
class StdMessenger implements _IMessenger {
|
||||
|
@ -40,7 +41,7 @@ class StdMessenger implements _IMessenger {
|
|||
"title" => [false, "TITLE!", null, "<color @b>T", "</color>", "==="],
|
||||
"desc" => ["DESC!", "<color @b>></color>", ""],
|
||||
"error" => ["CRIT.ERROR!", "<color @r>E!", "</color>"],
|
||||
"warn" => ["CRIT.WARN!", "<color @y>W!", "</color>"],
|
||||
"warning" => ["CRIT.WARNING!", "<color @y>W!", "</color>"],
|
||||
"note" => ["ATTENTION!", "<color @g>N!", "</color>"],
|
||||
"info" => ["IMPORTANT!", "<color @b>N!", "</color>"],
|
||||
"step" => ["*", "<color @w>.</color>", ""],
|
||||
|
@ -51,7 +52,7 @@ class StdMessenger implements _IMessenger {
|
|||
"title" => [false, "TITLE:", null, "<color @b>T</color><color b>", "</color>", "---"],
|
||||
"desc" => ["DESC:", "<color @b>></color>", ""],
|
||||
"error" => ["ERROR:", "<color @r>E</color><color r>", "</color>"],
|
||||
"warn" => ["WARN:", "<color @y>W</color><color y>", "</color>"],
|
||||
"warning" => ["WARNING:", "<color @y>W</color><color y>", "</color>"],
|
||||
"note" => ["NOTE:", "<color @g>N</color>", ""],
|
||||
"info" => ["INFO:", "<color @b>I</color>", ""],
|
||||
"step" => ["*", "<color @w>.</color>", ""],
|
||||
|
@ -62,7 +63,7 @@ class StdMessenger implements _IMessenger {
|
|||
"title" => [false, "title", null, "<color b>t", "</color>", null],
|
||||
"desc" => ["desc", "<color b>></color>", ""],
|
||||
"error" => ["error", "<color r>E</color><color -r>", "</color>"],
|
||||
"warn" => ["warn", "<color y>W</color><color -y>", "</color>"],
|
||||
"warning" => ["warning", "<color y>W</color><color -y>", "</color>"],
|
||||
"note" => ["note", "<color g>N</color>", ""],
|
||||
"info" => ["info", "<color b>I</color><color w>", "</color>"],
|
||||
"step" => ["*", "<color w>.</color>", ""],
|
||||
|
@ -73,7 +74,7 @@ class StdMessenger implements _IMessenger {
|
|||
"title" => [false, "title", null, "<color b>t", "</color>", null],
|
||||
"desc" => ["desc", "<color b>></color>", ""],
|
||||
"error" => ["debugE", "<color r>e</color><color -r>", "</color>"],
|
||||
"warn" => ["debugW", "<color y>w</color><color -y>", "</color>"],
|
||||
"warning" => ["debugW", "<color y>w</color><color -y>", "</color>"],
|
||||
"note" => ["debugN", "<color b>i</color>", ""],
|
||||
"info" => ["debug", "<color @w>D</color><color w>", "</color>"],
|
||||
"step" => ["*", "<color w>.</color>", ""],
|
||||
|
@ -306,7 +307,7 @@ class StdMessenger implements _IMessenger {
|
|||
}
|
||||
if ($printContent && $printResult) {
|
||||
if ($rcontent) {
|
||||
cl::ensure_array($content);
|
||||
A::ensure_array($content);
|
||||
$content[] = ": ";
|
||||
$content[] = $rcontent;
|
||||
}
|
||||
|
@ -579,18 +580,19 @@ class StdMessenger implements _IMessenger {
|
|||
if ($func !== null) {
|
||||
try {
|
||||
$result = $func($this);
|
||||
if ($result !== null) {
|
||||
if ($result === true) $this->asuccess();
|
||||
elseif ($result === false) $this->afailure();
|
||||
else $this->adone($result);
|
||||
if ($this->_getActionMark() > $until) {
|
||||
$this->aresult($result);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->afailure($e);
|
||||
throw $e;
|
||||
} finally {
|
||||
$this->_endAction($until);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function printActions(bool $endAction=false): void {
|
||||
function printActions(bool $endAction=false, ?int $overrideLevel=null): void {
|
||||
$this->printTitles();
|
||||
$err = $this->err;
|
||||
$indentLevel = $this->getIndentLevel(false);
|
||||
|
@ -599,7 +601,7 @@ class StdMessenger implements _IMessenger {
|
|||
foreach ($this->actions as &$action) {
|
||||
$mergeResult = $index++ == $lastIndex && $endAction;
|
||||
$linePrefix = $action["line_prefix"];
|
||||
$level = $action["level"];
|
||||
$level = $overrideLevel?? $action["level"];
|
||||
$content = $action["content"];
|
||||
$printContent = $action["print_content"];
|
||||
$rsuccess = $action["result_success"];
|
||||
|
@ -628,25 +630,33 @@ class StdMessenger implements _IMessenger {
|
|||
$this->_printGenericOrException($level, "step", $content, $this->getIndentLevel(), $this->err);
|
||||
}
|
||||
|
||||
function asuccess($content=null): void {
|
||||
function asuccess($content=null, ?int $overrideLevel=null): void {
|
||||
if (!$this->actions) $this->action(null);
|
||||
$this->action["result_success"] = true;
|
||||
$this->action["result_content"] = $content;
|
||||
$this->printActions(true);
|
||||
$this->printActions(true, $overrideLevel);
|
||||
}
|
||||
|
||||
function afailure($content=null): void {
|
||||
function afailure($content=null, ?int $overrideLevel=null): void {
|
||||
if (!$this->actions) $this->action(null);
|
||||
$this->action["result_success"] = false;
|
||||
$this->action["result_content"] = $content;
|
||||
$this->printActions(true);
|
||||
$this->printActions(true, $overrideLevel);
|
||||
}
|
||||
|
||||
function adone($content=null): void {
|
||||
function adone($content=null, ?int $overrideLevel=null): void {
|
||||
if (!$this->actions) $this->action(null);
|
||||
$this->action["result_success"] = null;
|
||||
$this->action["result_content"] = $content;
|
||||
$this->printActions(true);
|
||||
$this->printActions(true, $overrideLevel);
|
||||
}
|
||||
|
||||
function aresult($result=null, ?int $overrideLevel=null): void {
|
||||
if (!$this->actions) $this->action(null);
|
||||
if ($result === true) $this->asuccess(null, $overrideLevel);
|
||||
elseif ($result === false) $this->afailure(null, $overrideLevel);
|
||||
elseif ($result instanceof Exception) $this->afailure($result, $overrideLevel);
|
||||
else $this->adone($result, $overrideLevel);
|
||||
}
|
||||
|
||||
function _endAction(?int $until=null): void {
|
||||
|
@ -674,8 +684,8 @@ class StdMessenger implements _IMessenger {
|
|||
$this->_printGenericOrException($level, "note", $content, $this->getIndentLevel(), $this->err);
|
||||
}
|
||||
|
||||
function warn($content, ?int $level=null): void {
|
||||
$this->_printGenericOrException($level, "warn", $content, $this->getIndentLevel(), $this->err);
|
||||
function warning($content, ?int $level=null): void {
|
||||
$this->_printGenericOrException($level, "warning", $content, $this->getIndentLevel(), $this->err);
|
||||
}
|
||||
|
||||
function error($content, ?int $level=null): void {
|
||||
|
|
|
@ -3,10 +3,8 @@ namespace nulib\output\std;
|
|||
|
||||
use Exception;
|
||||
use nulib\cl;
|
||||
use nulib\os\file\Stream;
|
||||
use nulib\file\Stream;
|
||||
use nulib\php\content\content;
|
||||
use nulib\php\content\IContent;
|
||||
use nulib\php\content\IPrintable;
|
||||
|
||||
/**
|
||||
* Class StdOutput: affichage sur STDOUT, STDERR ou dans un fichier quelconque
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<?php
|
||||
namespace nulib\values;
|
||||
namespace nulib\php;
|
||||
|
||||
use ArrayAccess;
|
||||
use nulib\A;
|
||||
use nulib\cl;
|
||||
|
||||
/**
|
||||
|
@ -46,7 +47,7 @@ class akey {
|
|||
$array->offsetSet($key, ++$value);
|
||||
return $value;
|
||||
} else {
|
||||
cl::ensure_array($array);
|
||||
A::ensure_array($array);
|
||||
$value = (int)cl::get($array, $key);
|
||||
return $array[$key] = ++$value;
|
||||
}
|
||||
|
@ -59,7 +60,7 @@ class akey {
|
|||
if ($allow_negative || $value > 0) $array->offsetSet($key, --$value);
|
||||
return $value;
|
||||
} else {
|
||||
cl::ensure_array($array);
|
||||
A::ensure_array($array);
|
||||
$value = (int)cl::get($array, $key);
|
||||
if ($allow_negative || $value > 0) $array[$key] = --$value;
|
||||
return $value;
|
||||
|
@ -76,7 +77,7 @@ class akey {
|
|||
$value = cl::merge($value, $merge);
|
||||
$array->offsetSet($key, $value);
|
||||
} else {
|
||||
cl::ensure_array($array);
|
||||
A::ensure_array($array);
|
||||
$array[$key] = cl::merge($array[$key], $merge);
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +92,7 @@ class akey {
|
|||
cl::set($value, null, $value);
|
||||
$array->offsetSet($key, $value);
|
||||
} else {
|
||||
cl::ensure_array($array);
|
||||
A::ensure_array($array);
|
||||
cl::set($array[$key], null, $value);
|
||||
}
|
||||
}
|
|
@ -5,12 +5,13 @@ use ArrayAccess;
|
|||
use Countable;
|
||||
use Iterator;
|
||||
use nulib\cl;
|
||||
use nulib\IArrayWrapper;
|
||||
|
||||
/**
|
||||
* Class BaseArray: implémentation de base d'un objet array-like, qui peut aussi
|
||||
* servir comme front-end pour un array
|
||||
*/
|
||||
class BaseArray implements ArrayAccess, Countable, Iterator {
|
||||
class BaseArray implements ArrayAccess, Countable, Iterator, IArrayWrapper {
|
||||
function __construct(?array &$data=null) {
|
||||
$this->reset($data);
|
||||
}
|
||||
|
@ -18,10 +19,11 @@ class BaseArray implements ArrayAccess, Countable, Iterator {
|
|||
/** @var array */
|
||||
protected $data;
|
||||
|
||||
function &wrappedArray(): ?array { return $this->data; }
|
||||
|
||||
function __toString(): string { return var_export($this->data, true); }
|
||||
#function __debugInfo() { return $this->data; }
|
||||
function reset(?array &$data): void { $this->data =& $data; }
|
||||
function &array(): ?array { return $this->data; }
|
||||
function count(): int { return $this->data !== null? count($this->data): 0; }
|
||||
function keys(): array { return $this->data !== null? array_keys($this->data): []; }
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@ namespace nulib\php;
|
|||
|
||||
use Closure;
|
||||
use nulib\cl;
|
||||
use nulib\ValueException;
|
||||
use nulib\ref\php\ref_func;
|
||||
use nulib\schema\Schema;
|
||||
use nulib\ValueException;
|
||||
use ReflectionClass;
|
||||
use ReflectionFunction;
|
||||
use ReflectionMethod;
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
<?php
|
||||
namespace nulib\values;
|
||||
namespace nulib\php;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\str;
|
||||
use nulib\php\func;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
<?php
|
||||
namespace nulib\values;
|
||||
namespace nulib\php;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\str;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
|
|
@ -4,6 +4,11 @@ namespace nulib\php\time;
|
|||
use InvalidArgumentException;
|
||||
|
||||
class DateInterval extends \DateInterval {
|
||||
static function with($interval): self {
|
||||
if ($interval instanceof static) return $interval;
|
||||
else return new static($interval);
|
||||
}
|
||||
|
||||
protected static function to_string(DateInterval $interval) {
|
||||
$string = "P";
|
||||
$y = $interval->y;
|
||||
|
@ -24,6 +29,7 @@ class DateInterval extends \DateInterval {
|
|||
}
|
||||
|
||||
function __construct($duration) {
|
||||
if (is_int($duration)) $duration = "PT${duration}S";
|
||||
if ($duration instanceof \DateInterval) {
|
||||
$this->y = $duration->y;
|
||||
$this->m = $duration->m;
|
||||
|
|
|
@ -24,6 +24,11 @@ use InvalidArgumentException;
|
|||
* @property-read string $YmdHMSZ
|
||||
*/
|
||||
class DateTime extends \DateTime {
|
||||
static function with($datetime): self {
|
||||
if ($datetime instanceof static) return $datetime;
|
||||
else return new static($datetime);
|
||||
}
|
||||
|
||||
const DMY_PATTERN = '/^(\d+)\/(\d+)(?:\/(\d+))?$/';
|
||||
const YMD_PATTERN = '/^((?:\d{2})?\d{2})(\d{2})(\d{2})$/';
|
||||
const DMYHIS_PATTERN = '/^(\d+)\/(\d+)(?:\/(\d+))? +(\d+)[h:.](\d+)(?:[:.](\d+))?$/';
|
||||
|
|
|
@ -13,6 +13,7 @@ use InvalidArgumentException;
|
|||
* - une chaine de la forme "x[WDHMS]y" où x et y sont des nombres et la lettre
|
||||
* est l'unité de temps: W représente une semaine, D une journée, H une heure,
|
||||
* M une minute et S une seconde.
|
||||
* - la chaine "INF" qui représente une durée infinie
|
||||
*
|
||||
* Dans cette dernière forme, le timestamp destination est calculé en ajoutant x
|
||||
* unités. puis l'unité inférieure est ramenée à (0 + y)
|
||||
|
@ -24,6 +25,11 @@ use InvalidArgumentException;
|
|||
* NB: la valeur y pour l'unité S est ignorée
|
||||
*/
|
||||
class Delay {
|
||||
static function with($delay, ?DateTimeInterface $from=null): self {
|
||||
if ($delay instanceof static) return $delay;
|
||||
else return new static($delay, $from);
|
||||
}
|
||||
|
||||
/** valeurs par défaut de x et y pour les unités supportées */
|
||||
const DEFAULTS = [
|
||||
"w" => [0, 5],
|
||||
|
@ -35,6 +41,7 @@ class Delay {
|
|||
|
||||
static function compute_dest(int $x, string $u, ?int $y, DateTime $from): array {
|
||||
$dest = DateTime::clone($from);
|
||||
$yu = null;
|
||||
switch ($u) {
|
||||
case "w":
|
||||
if ($x > 0) {
|
||||
|
@ -43,29 +50,28 @@ class Delay {
|
|||
}
|
||||
$w = 7 - intval($dest->format("w"));
|
||||
$dest->add(new \DateInterval("P${w}D"));
|
||||
$u = "h";
|
||||
$yu = "h";
|
||||
break;
|
||||
case "d":
|
||||
$dest->add(new \DateInterval("P${x}D"));
|
||||
$u = "h";
|
||||
$yu = "h";
|
||||
break;
|
||||
case "h":
|
||||
$dest->add(new \DateInterval("PT${x}H"));
|
||||
$u = "m";
|
||||
$yu = "m";
|
||||
break;
|
||||
case "m":
|
||||
$dest->add(new \DateInterval("PT${x}M"));
|
||||
$u = "s";
|
||||
$yu = "s";
|
||||
break;
|
||||
case "s":
|
||||
$dest->add(new \DateInterval("PT${x}S"));
|
||||
$u = null;
|
||||
break;
|
||||
}
|
||||
if ($y !== null && $u !== null) {
|
||||
if ($y !== null && $yu !== null) {
|
||||
$h = intval($dest->format("H"));
|
||||
$m = intval($dest->format("i"));
|
||||
switch ($u) {
|
||||
switch ($yu) {
|
||||
case "h":
|
||||
$dest->setTime($y, 0, 0, 0);
|
||||
break;
|
||||
|
@ -77,13 +83,18 @@ class Delay {
|
|||
break;
|
||||
}
|
||||
}
|
||||
$repr = $y !== null? "$x$y$y": "$x";
|
||||
$u = strtoupper($u);
|
||||
$repr = $y !== null? "$x$u$y": "$x";
|
||||
return [$dest, $repr];
|
||||
}
|
||||
|
||||
function __construct($delay, ?DateTimeInterface $from=null) {
|
||||
if ($from === null) $from = new DateTime();
|
||||
if (is_int($delay)) {
|
||||
if ($delay === "INF") {
|
||||
$dest = DateTime::clone($from);
|
||||
$dest->add(new DateInterval("P9999Y"));
|
||||
$repr = "INF";
|
||||
} elseif (is_int($delay)) {
|
||||
[$dest, $repr] = self::compute_dest($delay, "s", null, $from);
|
||||
} elseif (is_string($delay) && preg_match('/^\d+$/', $delay)) {
|
||||
$x = intval($delay);
|
||||
|
@ -104,6 +115,13 @@ class Delay {
|
|||
$this->repr = $repr;
|
||||
}
|
||||
|
||||
function __serialize(): array {
|
||||
return [$this->dest, $this->repr];
|
||||
}
|
||||
function __unserialize(array $data): void {
|
||||
[$this->dest, $this->repr] = $data;
|
||||
}
|
||||
|
||||
/** @var DateTime */
|
||||
protected $dest;
|
||||
|
||||
|
@ -111,6 +129,22 @@ class Delay {
|
|||
return $this->dest;
|
||||
}
|
||||
|
||||
function addDuration($duration) {
|
||||
if (is_int($duration) && $duration < 0) {
|
||||
$this->dest->sub(DateInterval::with(-$duration));
|
||||
} else {
|
||||
$this->dest->add(DateInterval::with($duration));
|
||||
}
|
||||
}
|
||||
|
||||
function subDuration($duration) {
|
||||
if (is_int($duration) && $duration < 0) {
|
||||
$this->dest->add(DateInterval::with(-$duration));
|
||||
} else {
|
||||
$this->dest->sub(DateInterval::with($duration));
|
||||
}
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
protected $repr;
|
||||
|
||||
|
@ -125,7 +159,8 @@ class Delay {
|
|||
|
||||
/** retourner true si le délai imparti est écoulé */
|
||||
function isElapsed(?DateTimeInterface $now=null): bool {
|
||||
return $this->_getDiff($now)->invert == 0;
|
||||
if ($this->repr === "INF") return false;
|
||||
else return $this->_getDiff($now)->invert == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace nulib\values;
|
||||
namespace nulib\php;
|
||||
|
||||
use ArrayAccess;
|
||||
use nulib\str;
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace nulib\values;
|
||||
namespace nulib\php;
|
||||
|
||||
use ArrayAccess;
|
||||
use nulib\str;
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace nulib\ref\os\csv;
|
||||
namespace nulib\ref\file\csv;
|
||||
|
||||
/**
|
||||
* Class ref_csv: références des valeurs normalisées pour les fichiers CSV à
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
namespace nulib\schema;
|
||||
|
||||
class OldSchema {
|
||||
/**
|
||||
* @var bool true si toutes les clés du schéma doivent exister, avec leur
|
||||
* valeur par défaut le cas échéant
|
||||
*/
|
||||
protected $ppEnsureKeys;
|
||||
|
||||
/** @var bool true si les clés doivent être dans l'ordre du schéma */
|
||||
protected $ppOrderKeys;
|
||||
|
||||
/**
|
||||
* @var bool true si les valeurs doivent être automatiquement vérifiées et
|
||||
* corrigées
|
||||
*/
|
||||
protected $ppVerifix;
|
||||
|
||||
function ensureSchema(&$value, $key=null, ?array $params=null): void {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
# nulib\schema
|
||||
|
||||
objet: s'assurer que des données soit dans un type particulier, en les
|
||||
convertissant si nécessaire. la source de ces données peut-être diverse:
|
||||
formulaire web, résultat d'une requête SQL, flux CSV, etc.
|
||||
|
||||
les données dont on peut modéliser le schéma sont de 3 types:
|
||||
* scalaire
|
||||
* tableau associatif
|
||||
* liste (tableau séquentiel ou associatif d'éléments du même type)
|
||||
|
||||
chaque type de données a une syntaxe spécifique pour la définition du schéma.
|
||||
|
||||
## Nature de schéma
|
||||
|
||||
Un schéma se présente sous la forme d'un tableau associatif avec des clés qui
|
||||
dépendent de la nature du schéma. La nature du schéma est indiquée avec la clé
|
||||
`""` (chaine vide), e.g
|
||||
~~~php
|
||||
const SCHEMA = [
|
||||
"" => NATURE,
|
||||
];
|
||||
~~~
|
||||
|
||||
La nature indique le type de données représenté par le schéma.
|
||||
* nature scalaire: modélise une donnée scalaire
|
||||
~~~php
|
||||
const SCALAR_SCHEMA = [
|
||||
$type, [$default, $title, ...]
|
||||
"" => "scalar",
|
||||
];
|
||||
~~~
|
||||
Si le type est "array" ou "?array", on peut préciser le schéma de la donnée
|
||||
~~~php
|
||||
const SCALAR_SCHEMA = [
|
||||
"?array", [$default, $title, ...]
|
||||
"" => "scalar",
|
||||
"schema" => NAKED_SCHEMA,
|
||||
];
|
||||
~~~
|
||||
* nature tableau associatif: modélise un tableau associatif (le tableau peut
|
||||
avoir des clés numériques ou chaines --> seules les clés décrites par le
|
||||
schéma sont validées)
|
||||
~~~php
|
||||
const ASSOC_SCHEMA = [
|
||||
KEY => VALUE_SCHEMA,
|
||||
...
|
||||
"" => "assoc",
|
||||
];
|
||||
~~~
|
||||
la nature "tableau associatif" est du sucre syntaxique pour une valeur
|
||||
scalaire de type "?array" dont on précise le schéma
|
||||
~~~php
|
||||
// la valeur ci-dessus est strictement équivalent à
|
||||
const ASSOC_SCHEMA = [
|
||||
"?array",
|
||||
"" => "scalar",
|
||||
"schema" => [
|
||||
KEY => VALUE_SCHEMA,
|
||||
...
|
||||
],
|
||||
];
|
||||
~~~
|
||||
|
||||
* nature liste: modélise une liste de valeurs du même type (le tableau peut
|
||||
avoir des clés numériques ou chaines --> on ne modélise ni le type ni la
|
||||
valeur des clés)
|
||||
~~~php
|
||||
const LIST_SCHEMA = [
|
||||
"?array", [$default, $title, ...]
|
||||
"" => "list",
|
||||
"schema" => ITEM_SCHEMA,
|
||||
];
|
||||
~~~
|
||||
|
||||
## Schéma d'une valeur scalaire
|
||||
|
||||
Dans sa forme normalisée, une valeur scalaire est généralement modélisée de
|
||||
cette manière:
|
||||
~~~php
|
||||
const SCALAR_SCHEMA = [
|
||||
"type" => "types autorisés de la valeur",
|
||||
"default" => "valeur par défaut si la valeur n'existe pas",
|
||||
"title" => "libellé de la valeur, utilisable par exemple dans un formulaire",
|
||||
"required" => "la valeur est-elle requise? si oui, elle doit exister",
|
||||
"nullable" => "si la valeur existe, peut-elle être nulle?",
|
||||
"desc" => "description de la valeur",
|
||||
"checker_func" => "une fonction qui vérifie une valeur et la classifie",
|
||||
"parser_func" => "une fonction qui analyse une chaine pour produire la valeur",
|
||||
"messages" => "messages à afficher en cas d'erreur d'analyse",
|
||||
"formatter_func" => "une fonction qui formatte la valeur pour affichage",
|
||||
"format" => "format à utiliser pour l'affichage",
|
||||
"" => "scalar",
|
||||
"schema" => "schéma de la valeur si le type est array ou ?array, null sinon",
|
||||
];
|
||||
~~~
|
||||
|
||||
L'ordre des clés du schéma ci-dessus indique la clé associé à une valeur si elle
|
||||
est fournie dans un tableau séquentiel. Par exemple, les deux schéma suivants
|
||||
sont équivalents:
|
||||
~~~php
|
||||
const SCALAR_SCHEMA1 = [
|
||||
"string", null, "une valeur chaine",
|
||||
];
|
||||
const SCALAR_SCHEMA2 = [
|
||||
"type" => "string",
|
||||
"default" => null,
|
||||
"title" => "une valeur chaine",
|
||||
"" => "scalar",
|
||||
];
|
||||
~~~
|
||||
|
||||
Si la nature du schéma n'est pas spécifiée, on considère que c'est un schéma de
|
||||
nature scalaire si:
|
||||
* c'est une chaine, qui représente alors le type, e.g `"string"`
|
||||
* c'est un tableau avec un unique élément à l'index 0 de type chaine, qui est
|
||||
aussi le type, e.g `["string"]`
|
||||
* c'est un tableau avec un élément à l'index 0, ainsi que d'autres éléments,
|
||||
e.g `["string", null, "required" => true]`
|
||||
|
||||
message indique les messages à afficher en cas d'erreur d'analyse. les clés sont
|
||||
normalisées et correspondent à différents états de la valeur tels qu'analysés
|
||||
par `checker_func`
|
||||
~~~php
|
||||
const MESSAGE_SCHEMA = [
|
||||
"missing" => "message si la valeur n'existe pas dans la source et qu'elle est requise",
|
||||
"unavailable" => "message si la valeur vaut false dans la source et qu'elle est requise",
|
||||
"null" => "message si la valeur est nulle et qu'elle n'est pas nullable",
|
||||
"empty" => "message si la valeur est une chaine vide et que ce n'est pas autorisé",
|
||||
"invalid" => "message si la valeur est invalide",
|
||||
];
|
||||
~~~
|
||||
|
||||
## Schéma d'un tableau associatif
|
||||
|
||||
Dans sa forme *non normalisée*, un tableau associatif est généralement modélisé
|
||||
de cette manière:
|
||||
~~~php
|
||||
const ASSOC_SCHEMA = [
|
||||
KEY => VALUE_SCHEMA,
|
||||
...
|
||||
"" => "assoc",
|
||||
];
|
||||
~~~
|
||||
où chaque occurrence de `KEY => VALUE_SCHEMA` définit le schéma de la valeur
|
||||
dont la clé est `KEY`
|
||||
|
||||
Si la nature du schéma n'est pas spécifiée, on considère que c'est un schéma de
|
||||
nature associative si:
|
||||
* c'est un tableau uniquement associatif avec aucun élément séquentiel, e.g
|
||||
`["name" => "string", "age" => "int"]`
|
||||
|
||||
VALUE_SCHEMA peut-être n'importe quel schéma valide, qui sera analysé
|
||||
récursivement, avec cependant l'ajout de quelques clés supplémentaires:
|
||||
* description de la valeur dans le contexte du tableau
|
||||
~~~php
|
||||
VALUE_SCHEMA = [
|
||||
...
|
||||
"name" => "identifiant de la valeur",
|
||||
"pkey" => "chemin de clé de la valeur dans le tableau associatif",
|
||||
];
|
||||
~~~
|
||||
* s'il s'agit d'une valeur scalaire simple autre que array
|
||||
~~~php
|
||||
VALUE_SCHEMA = [
|
||||
...
|
||||
"header" => "nom de l'en-tête s'il faut présenter cette donnée dans un tableau",
|
||||
"composite" => "ce champ fait-il partie d'une valeur composite?",
|
||||
];
|
||||
~~~
|
||||
|
||||
## Schéma d'une liste (tableau séquentiel ou associatif d'éléments du même type)
|
||||
|
||||
Dans sa forme *non normalisée*, une liste est généralement modélisée de cette
|
||||
manière:
|
||||
~~~php
|
||||
const LIST_SCHEMA = [ITEM_SCHEMA];
|
||||
~~~
|
||||
où ITEM_SCHEMA est le schéma des éléments de la liste
|
||||
|
||||
Pour information, la forme normalisée est plutôt de la forme
|
||||
~~~php
|
||||
const LIST_SCHEMA = [
|
||||
"?array",
|
||||
"" => "list",
|
||||
"schema" => ITEM_SCHEMA,
|
||||
];
|
||||
~~~
|
||||
le type "?array" ou "array" indique si la liste est nullable ou non. la valeur
|
||||
par défaut est "?array"
|
||||
|
||||
Si la nature du schéma n'est pas spécifiée, on considère que c'est un schéma de
|
||||
nature liste si:
|
||||
* c'est un tableau avec un unique élément de type tableau à l'index 0, e.g
|
||||
`[["string", null, "required" => true]]`
|
||||
|
||||
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
namespace nulib\schema;
|
||||
|
||||
use nulib\schema\_assoc\AssocResult;
|
||||
use nulib\schema\_list\ListResult;
|
||||
use nulib\schema\_scalar\ScalarResult;
|
||||
|
||||
/**
|
||||
* Class Result: résultat de l'analyse ou de la normalisation d'une valeur
|
||||
*/
|
||||
abstract class Result {
|
||||
function __construct() {
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
function isAssoc(?AssocResult &$assoc=null): bool { return false; }
|
||||
function isList(?ListResult &$list=null): bool { return false; }
|
||||
function isScalar(?ScalarResult &$scalar=null): bool { return false; }
|
||||
|
||||
/**
|
||||
* Obtenir la liste des clés valides pour les valeurs accessibles via cet
|
||||
* objet
|
||||
*/
|
||||
abstract function getKeys(): array;
|
||||
|
||||
/** obtenir un objet pour gérer la valeur spécifiée */
|
||||
abstract function getResult($key=null): Result;
|
||||
|
||||
abstract function reset(): void;
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
namespace nulib\schema;
|
||||
|
||||
use ArrayAccess;
|
||||
use nulib\AccessException;
|
||||
use nulib\cl;
|
||||
use nulib\schema\_assoc\AssocSchema;
|
||||
use nulib\schema\_list\ListSchema;
|
||||
use nulib\schema\_scalar\ScalarSchema;
|
||||
|
||||
abstract class Schema implements ArrayAccess {
|
||||
/**
|
||||
* créer si besoin une nouvelle instance de {@link Schema} à partir d'une
|
||||
* définition de schéma
|
||||
*
|
||||
* - si $schema est une instance de schéma, la retourner
|
||||
* - si $schema est un array, c'est une définition, et elle est remplacée par
|
||||
* l'instance de Schema nouvelle créée
|
||||
* - sinon, prendre $definition comme définition
|
||||
*/
|
||||
static function ns(&$schema, $definition=null, $definitionKey=null): self {
|
||||
if (is_array($schema)) {
|
||||
$definition = $schema;
|
||||
$schema = null;
|
||||
}
|
||||
if ($schema === null) {
|
||||
if (AssocSchema::isa_definition($definition)) {
|
||||
$schema = new AssocSchema($definition, $definitionKey);
|
||||
} elseif (ListSchema::isa_definition($definition)) {
|
||||
$schema = new ListSchema($definition, $definitionKey);
|
||||
} elseif (ScalarSchema::isa_definition($definition)) {
|
||||
$schema = new ScalarSchema($definition, $definitionKey);
|
||||
} else {
|
||||
throw SchemaException::invalid_schema();
|
||||
}
|
||||
}
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Créer si besoin une nouvelle instance de {@link Value} qui référence la
|
||||
* variable $dest (si $destKey===null) ou $dest[$destKey] si $destKey n'est
|
||||
* pas null
|
||||
*/
|
||||
static function nv(?Value &$destv=null, &$dest=null, $destKey=null, &$schema=null, $definition=null): Value {
|
||||
if ($definition === null) {
|
||||
# bien que techniquement, $definition peut être null (il s'agit alors du
|
||||
# schéma d'un scalaire quelconque), on ne l'autorise pas ici
|
||||
throw SchemaException::invalid_schema("definition is required");
|
||||
}
|
||||
return self::ns($schema, $definition)->newValue($destv, $dest, $destKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array définition du schéma, à redéfinir le cas échéant dans une classe
|
||||
* dérivée
|
||||
*/
|
||||
const SCHEMA = null;
|
||||
|
||||
/** @var array */
|
||||
protected $definition;
|
||||
|
||||
/** retourner true si le schéma est de nature tableau associatif */
|
||||
function isAssoc(?AssocSchema &$assoc=null): bool { return false; }
|
||||
/** retourner true si le schéma est de nature liste */
|
||||
function isList(?ListSchema &$list=null): bool { return false; }
|
||||
/** retourner true si le schéma est de nature scalaire */
|
||||
function isScalar(?ScalarSchema &$scalar=null): bool { return false; }
|
||||
|
||||
abstract function newValue(?Value &$destv=null, &$dest=null, $destKey=null): Value;
|
||||
|
||||
#############################################################################
|
||||
# key & properties
|
||||
|
||||
function offsetExists($offset): bool {
|
||||
return array_key_exists($offset, $this->definition);
|
||||
}
|
||||
function offsetGet($offset) {
|
||||
if (!array_key_exists($offset, $this->definition)) return null;
|
||||
else return $this->definition[$offset];
|
||||
}
|
||||
function offsetSet($offset, $value): void {
|
||||
throw AccessException::read_only(null, $offset);
|
||||
}
|
||||
function offsetUnset($offset): void {
|
||||
throw AccessException::read_only(null, $offset);
|
||||
}
|
||||
|
||||
const _PROPERTY_PKEYS = [];
|
||||
function __get($name) {
|
||||
$pkey = cl::get(static::_PROPERTY_PKEYS, $name, $name);
|
||||
return cl::pget($this->definition, $pkey);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
namespace nulib\schema;
|
||||
|
||||
use Exception;
|
||||
|
||||
class SchemaException extends Exception {
|
||||
static final function invalid_schema(?string $message=null): self {
|
||||
$invalid_schema = "invalid schema";
|
||||
if ($message !== null) $invalid_schema .= ": $message";
|
||||
return new static($invalid_schema);
|
||||
}
|
||||
|
||||
static final function invalid_kind(string $kind, $value, ?string $message=null): self {
|
||||
$invalid_kind = var_export($value, true).": invalid $kind";
|
||||
if ($message !== null) $invalid_kind .= ": $message";
|
||||
return new static($invalid_kind);
|
||||
}
|
||||
|
||||
static final function invalid_type($value, ?string $message=null): self {
|
||||
return self::invalid_kind("type", $value, $message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
# nulib\schema
|
||||
|
||||
* implémenter support `analyzer_func`, `extractor_func`, `parser_func`,
|
||||
`normalizer_func`, `formatter_func`
|
||||
* dans AssocSchema, support `[key_prefix]` qui permet de spécifier un préfixe
|
||||
commun aux champs dans le tableau destination, e.g
|
||||
~~~php
|
||||
$value = Schema::ns($schema, [
|
||||
"a" => "?string",
|
||||
"b" => "?int",
|
||||
])->newValue();
|
||||
$dest = ["x_a" => 5, "x_b" => "10"],
|
||||
$value->reset($dest, null, [
|
||||
"key_prefix" => "x_",
|
||||
]);
|
||||
# $dest vaut ["x_a" => "5", "x_b" => 10];
|
||||
~~~
|
||||
définir si le préfixe doit être spécifié sur le schéma ou sur la valeur...
|
||||
actuellement, le code ne permet pas de définir de tels paramètres...
|
||||
|
||||
alternative: c'est lors de la *définition* du schéma que le préfixe est ajouté
|
||||
e.g
|
||||
~~~php
|
||||
$value = Schema::ns($schema, [
|
||||
"a" => "?string",
|
||||
"b" => "?int",
|
||||
], [
|
||||
"key_prefix" => "x_",
|
||||
])->newValue();
|
||||
$dest = ["x_a" => 5, "x_b" => "10"],
|
||||
$value->reset($dest);
|
||||
# $dest vaut ["x_a" => "5", "x_b" => 10];
|
||||
~~~
|
||||
* dans la définition, `[type]` est remplacé par l'instance de IType lors de sa
|
||||
résolution?
|
||||
* implémenter l'instanciation de types avec des paramètres particuliers. *si*
|
||||
des paramètres sont fournis, le type est instancié avec la signature
|
||||
`IType($typeDefinition, $schemaDefinition)` e.g
|
||||
~~~php
|
||||
const SCHEMA = ["type", default, "required" => true];
|
||||
# le type est instancié comme suit:
|
||||
$type = new ttype();
|
||||
|
||||
const SCHEMA = [[["type", ...]], default, "required" => true];
|
||||
# le type est instancié comme suit:
|
||||
# le tableau peut être une liste ou associatif, c'est au type de décider ce
|
||||
# qu'il en fait
|
||||
$type = new ttype(["type", ...], SCHEMA);
|
||||
~~~
|
||||
* ajouter à IType les méthodes getName() pour le nom officiel du type,
|
||||
getAliases() pour les alias supportés, et getClass() pour la définition de la
|
||||
classe dans les méthodes et propriétés
|
||||
getName() et getAliases() sont juste pour information, ils ne sont pas utilisés
|
||||
lors de la résolution du type effectif.
|
||||
* si cela a du sens, dans AssocSchema, n'instancier les schémas de chaque clé qu'à la demande.
|
||||
l'idée est de ne pas perdre du temps à instancier un schéma qui ne serait pas utilisé
|
||||
|
||||
on pourrait avoir d'une manière générale quelque chose comme:
|
||||
~~~
|
||||
Schema::ensure(&$schema, ?array $def=null, $defKey=null): Schema;
|
||||
~~~
|
||||
* si $schema est une instance de Schema, la retourner
|
||||
* si c'est un array, c'est une définition et il faut la remplacer par l'instance de Schema correspondant
|
||||
* sinon, prendre $def comme définition
|
||||
$key est la clé si $schema est dans un autre schema
|
||||
* actuellement, pour un schéma associatif, si on normalise un tableau séquentiel,
|
||||
chaque valeur correspond à la clé de même rang, eg. pour un schéma
|
||||
~~~php
|
||||
const SCHEMA = ["first" => DEF, "second" => DEF];
|
||||
const ARRAY = ["first", "second"];
|
||||
~~~
|
||||
la valeur normalisée de `ARRAY` est `["first" => "first", "second" => "second"]`
|
||||
|
||||
cependant, dans certaines circonstances (notamment pour des paramètres), on
|
||||
devrait pouvoir considérer une valeur indexée comme un flag, i.e la valeur
|
||||
normalisée de ARRAY serait `["first" => true, "second" => true]`
|
||||
|
||||
la définition de ces "circonstances" est encore à faire: soit un paramètre
|
||||
lors de la définition du schéma, soit un truc magique du genre "toutes les
|
||||
valeurs séquentielles sont des clés du schéma"
|
||||
|
||||
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
namespace nulib\schema;
|
||||
|
||||
use ArrayAccess;
|
||||
use IteratorAggregate;
|
||||
use nulib\schema\_assoc\AssocValue;
|
||||
use nulib\schema\_list\ListValue;
|
||||
use nulib\schema\_scalar\ScalarValue;
|
||||
use nulib\schema\types\IType;
|
||||
|
||||
abstract class Value implements ArrayAccess, IteratorAggregate {
|
||||
function isAssoc(?AssocValue &$assoc=null): bool { return false; }
|
||||
function isList(?ListValue &$list=null): bool { return false; }
|
||||
function isScalar(?ScalarValue &$scalar=null): bool { return false; }
|
||||
|
||||
/** spécifier la valeur destination gérée par cet objet */
|
||||
abstract function reset(&$dest, $destKey=null, ?bool $verifix=null): self;
|
||||
|
||||
/**
|
||||
* Obtenir la liste des clés valides pour les valeurs accessibles via cet
|
||||
* objet
|
||||
*/
|
||||
abstract function getKeys(): array;
|
||||
|
||||
/** obtenir un objet pour gérer la valeur spécifiée */
|
||||
abstract function getValue($key=null): Value;
|
||||
|
||||
function getIterator() {
|
||||
foreach ($this->getKeys() as $key) {
|
||||
yield $key => $this->getValue($key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* obtenir le résultat de l'appel d'une des fonctions {@link set()} ou
|
||||
* {@link unset()}
|
||||
*/
|
||||
abstract function getResult(): Result;
|
||||
|
||||
/** retourner true si la valeur existe */
|
||||
abstract function isPresent(): bool;
|
||||
|
||||
/** retourner le type associé à la valeur */
|
||||
abstract function getType(): IType;
|
||||
|
||||
/** retourner true si la valeur est disponible */
|
||||
abstract function isAvailable(): bool;
|
||||
|
||||
/** retourner true si la valeur est valide */
|
||||
abstract function isValid(): bool;
|
||||
|
||||
/** retourner true si la valeur est dans sa forme normalisée */
|
||||
abstract function isNormalized(): bool;
|
||||
|
||||
/** obtenir la valeur */
|
||||
abstract function get($default=null);
|
||||
|
||||
/** remplacer la valeur */
|
||||
abstract function set($value): self;
|
||||
|
||||
/** supprimer la valeur */
|
||||
abstract function unset(): self;
|
||||
|
||||
/** formatter la valeur pour affichage */
|
||||
abstract function format($format=null): string;
|
||||
|
||||
#############################################################################
|
||||
# key & properties
|
||||
|
||||
function offsetExists($offset): bool {
|
||||
return in_array($offset, $this->getKeys());
|
||||
}
|
||||
|
||||
function offsetGet($offset) {
|
||||
return $this->getValue($offset);
|
||||
}
|
||||
|
||||
function offsetSet($offset, $value): void {
|
||||
$this->getValue($offset)->set($value);
|
||||
}
|
||||
|
||||
function offsetUnset($offset): void {
|
||||
$this->getValue($offset)->unset();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
namespace nulib\schema\_assoc;
|
||||
|
||||
use nulib\schema\Result;
|
||||
|
||||
class AssocResult extends Result {
|
||||
function isAssoc(?AssocResult &$assoc=null): bool { $assoc = $this; return true;}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
namespace nulib\schema\_assoc;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\ref\schema\ref_schema;
|
||||
use nulib\schema\Schema;
|
||||
use nulib\schema\Value;
|
||||
|
||||
/**
|
||||
* Class AssocSchema
|
||||
*/
|
||||
class AssocSchema extends Schema {
|
||||
/** @var array meta-schema d'un schéma de nature tableau associatif */
|
||||
const METASCHEMA = ref_schema::ASSOC_METASCHEMA;
|
||||
|
||||
/**
|
||||
* indiquer si $definition est une définition de schéma de nature tableau
|
||||
* associatif que {@link normalize()} pourrait normaliser
|
||||
*/
|
||||
static function isa_definition($definition): bool {
|
||||
if (!is_array($definition)) return false;
|
||||
# nature explicitement spécifiée
|
||||
if (array_key_exists("", $definition)) {
|
||||
$nature = $definition[""];
|
||||
if ($nature === "assoc") return true;
|
||||
if (is_array($nature)
|
||||
&& array_key_exists(0, $nature)
|
||||
&& $nature[0] === "assoc") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
# un tableau associatif
|
||||
return !cl::have_num_keys($definition);
|
||||
}
|
||||
|
||||
static function normalize($definition, $definitionKey=null): array {
|
||||
}
|
||||
|
||||
function __construct($definition=null, $definitionKey=null, bool $normalize=true) {
|
||||
if ($definition === null) $definition = static::SCHEMA;
|
||||
if ($normalize) $definition = self::normalize($definition, $definitionKey);
|
||||
$this->definition = $definition;
|
||||
}
|
||||
|
||||
function isAssoc(?AssocSchema &$assoc=null): bool {
|
||||
$assoc = $this;
|
||||
return true;
|
||||
}
|
||||
|
||||
function newValue(?Value &$destv=null, &$dest=null, $destKey=null): AssocValue {
|
||||
if ($destv instanceof AssocValue) return $destv->reset($dest, $destKey);
|
||||
else return ($destv = new AssocValue($this, $dest, $destKey));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
namespace nulib\schema\_assoc;
|
||||
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Value;
|
||||
|
||||
class AssocValue extends Value {
|
||||
function isAssoc(?AssocValue &$assoc=null): bool { $assoc = $this; return true; }
|
||||
|
||||
function ensureKeys(): bool {
|
||||
}
|
||||
function orderKeys(): bool {
|
||||
}
|
||||
|
||||
/** @param Result[] $results */
|
||||
function verifix(bool $throw=true, ?array &$results=null): bool {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
namespace nulib\schema\_list;
|
||||
|
||||
use nulib\schema\Result;
|
||||
|
||||
class ListResult extends Result {
|
||||
function isList(?ListResult &$list=null): bool { $list = $this; return true;}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
namespace nulib\schema\_list;
|
||||
|
||||
use nulib\ref\schema\ref_schema;
|
||||
use nulib\schema\Schema;
|
||||
use nulib\schema\Value;
|
||||
|
||||
class ListSchema extends Schema {
|
||||
/** @var array meta-schema d'un schéma de nature liste */
|
||||
const METASCHEMA = ref_schema::LIST_METASCHEMA;
|
||||
|
||||
/**
|
||||
* indiquer si $definition est une définition de schéma de nature liste que
|
||||
* {@link normalize()} pourrait normaliser
|
||||
*/
|
||||
static function isa_definition($definition): bool {
|
||||
if (!is_array($definition)) return false;
|
||||
# nature explicitement spécifiée
|
||||
if (array_key_exists("", $definition)) {
|
||||
$nature = $definition[""];
|
||||
if ($nature === "list") return true;
|
||||
if (is_array($nature)
|
||||
&& array_key_exists(0, $nature)
|
||||
&& $nature[0] === "list") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
# un unique élément tableau à l'index 0
|
||||
$count = count($definition);
|
||||
$haveIndex0 = array_key_exists(0, $definition);
|
||||
return $count == 1 && $haveIndex0 && is_array($definition[0]);
|
||||
}
|
||||
|
||||
static function normalize($definition, $definitionKey=null): array {
|
||||
}
|
||||
|
||||
function __construct($definition=null, $definitionKey=null, bool $normalize=true) {
|
||||
if ($definition === null) $definition = static::SCHEMA;
|
||||
if ($normalize) $definition = self::normalize($definition, $definitionKey);
|
||||
$this->definition = $definition;
|
||||
}
|
||||
|
||||
function isList(?ListSchema &$list=null): bool {
|
||||
$list = $this;
|
||||
return true;
|
||||
}
|
||||
|
||||
function newValue(?Value &$destv=null, &$dest=null, $destKey=null): ListValue {
|
||||
if ($destv instanceof ListValue) return $destv->reset($dest, $destKey);
|
||||
else return ($destv = new ListValue($this, $dest, $destKey));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
namespace nulib\schema\_list;
|
||||
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Value;
|
||||
|
||||
class ListValue extends Value {
|
||||
function isList(?ListValue &$list=null): bool { $list = $this; return true; }
|
||||
|
||||
function ensureKeys(): bool {
|
||||
}
|
||||
|
||||
function orderKeys(): bool {
|
||||
}
|
||||
|
||||
/** @param Result[] $results */
|
||||
function verifix(bool $throw=true, ?array &$results=null): bool {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
<?php
|
||||
namespace nulib\schema\_scalar;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\ref\schema\ref_analyze;
|
||||
use nulib\ref\schema\ref_schema;
|
||||
use nulib\schema\Result;
|
||||
use nulib\ValueException;
|
||||
|
||||
/**
|
||||
* Class ScalarResult: résultat de l'analyse ou de la normalisation d'une valeur
|
||||
*
|
||||
* @property bool $resultAvailable le résultat est-il disponible?
|
||||
* @property bool $present la valeur existe-t-elle?
|
||||
* @property bool $available si la valeur existe, est-elle disponible?
|
||||
* @property bool $null si la valeur est disponible, est-elle nulle?
|
||||
* @property bool $valid si la valeur est disponible, est-elle valide?
|
||||
* @property bool $normalized si la valeur est valide, est-elle normalisée?
|
||||
* @property string|null $orig valeur originale avant analyse avec parse()
|
||||
* @property string|null $message message si la valeur n'est pas valide
|
||||
*/
|
||||
class ScalarResult extends Result {
|
||||
const KEYS = ["resultAvailable", "present", "available", "null", "valid", "normalized", "orig", "message"];
|
||||
|
||||
function isScalar(?ScalarResult &$scalar=null): bool { $scalar = $this; return true; }
|
||||
|
||||
function getKeys(): array {
|
||||
return [null];
|
||||
}
|
||||
|
||||
function getResult($key=null): Result {
|
||||
if ($key === null) return $this;
|
||||
else throw ValueException::invalid_key($key);
|
||||
}
|
||||
|
||||
/** @var array */
|
||||
protected $result;
|
||||
|
||||
function reset(): void {
|
||||
$this->result = array_merge(
|
||||
array_fill_keys(static::KEYS, null), [
|
||||
"resultAvailable" => false,
|
||||
"present" => false,
|
||||
"available" => false,
|
||||
"null" => false,
|
||||
"valid" => false,
|
||||
"normalized" => false,
|
||||
]);
|
||||
}
|
||||
|
||||
function __get(string $name) {
|
||||
return $this->result[$name];
|
||||
}
|
||||
|
||||
function __set(string $name, $value): void {
|
||||
$this->result[$name] = $value;
|
||||
}
|
||||
|
||||
protected static function replace_key(string &$message, ?string $key): void {
|
||||
if ($key) {
|
||||
$message = str_replace("{key}", $key, $message);
|
||||
} else {
|
||||
$message = str_replace("{key}: ", "", $message);
|
||||
$message = str_replace("cette valeur", "la valeur", $message);
|
||||
}
|
||||
}
|
||||
|
||||
protected static function replace_orig(string &$message, $orig): void {
|
||||
$message = str_replace("{orig}", strval($orig), $message);
|
||||
}
|
||||
|
||||
protected function getMessage(string $key, ScalarSchema $schema): string {
|
||||
$message = cl::get($schema->messages, $key);
|
||||
if ($message !== null) return $message;
|
||||
return cl::get(ref_schema::MESSAGES, $key);
|
||||
}
|
||||
|
||||
function setMissing(ScalarSchema $schema): int {
|
||||
$this->resultAvailable = true;
|
||||
$this->present = false;
|
||||
$this->available = false;
|
||||
if (!$schema->required) {
|
||||
$this->null = false;
|
||||
$this->valid = true;
|
||||
$this->normalized = true;
|
||||
return ref_analyze::NORMALIZED;
|
||||
} else {
|
||||
$message = $this->getMessage("missing", $schema);
|
||||
self::replace_key($message, $schema->name);
|
||||
$this->message = $message;
|
||||
return ref_analyze::MISSING;
|
||||
}
|
||||
}
|
||||
|
||||
function setUnavailable(ScalarSchema $schema): int {
|
||||
$this->resultAvailable = true;
|
||||
$this->present = true;
|
||||
$this->available = false;
|
||||
if (!$schema->required) {
|
||||
$this->null = false;
|
||||
$this->valid = true;
|
||||
$this->normalized = true;
|
||||
return ref_analyze::NORMALIZED;
|
||||
} else {
|
||||
$message = $this->getMessage("unavailable", $schema);
|
||||
self::replace_key($message, $schema->name);
|
||||
$this->message = $message;
|
||||
return ref_analyze::UNAVAILABLE;
|
||||
}
|
||||
}
|
||||
|
||||
function setNull(ScalarSchema $schema): int {
|
||||
$this->resultAvailable = true;
|
||||
$this->present = true;
|
||||
$this->available = true;
|
||||
$this->null = true;
|
||||
if ($schema->nullable) {
|
||||
$this->valid = true;
|
||||
$this->normalized = true;
|
||||
return ref_analyze::NORMALIZED;
|
||||
} else {
|
||||
$message = $this->getMessage("null", $schema);
|
||||
self::replace_key($message, $schema->name);
|
||||
$this->message = $message;
|
||||
return ref_analyze::NULL;
|
||||
}
|
||||
}
|
||||
|
||||
function setInvalid($value, ScalarSchema $schema): int {
|
||||
$this->resultAvailable = true;
|
||||
$this->present = true;
|
||||
$this->available = true;
|
||||
$this->null = false;
|
||||
$this->valid = false;
|
||||
$this->orig = $value;
|
||||
$message = $this->getMessage("invalid", $schema);
|
||||
self::replace_key($message, $schema->name);
|
||||
self::replace_orig($message, $schema->orig);
|
||||
$this->message = $message;
|
||||
return ref_analyze::INVALID;
|
||||
}
|
||||
|
||||
function setValid(): int {
|
||||
$this->resultAvailable = true;
|
||||
$this->present = true;
|
||||
$this->available = true;
|
||||
$this->null = false;
|
||||
$this->valid = true;
|
||||
return ref_analyze::VALID;
|
||||
}
|
||||
|
||||
function setNormalized(): int {
|
||||
$this->resultAvailable = true;
|
||||
$this->present = true;
|
||||
$this->available = true;
|
||||
$this->null = false;
|
||||
$this->valid = true;
|
||||
$this->normalized = true;
|
||||
return ref_analyze::NORMALIZED;
|
||||
}
|
||||
|
||||
function throw(bool $throw): void {
|
||||
if ($throw) throw new ValueException($this->message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
<?php
|
||||
namespace nulib\schema\_scalar;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\ref\schema\ref_schema;
|
||||
use nulib\ref\schema\ref_types;
|
||||
use nulib\schema\Schema;
|
||||
use nulib\schema\SchemaException;
|
||||
use nulib\schema\types\tarray;
|
||||
use nulib\schema\types\tbool;
|
||||
use nulib\schema\types\tcallable;
|
||||
use nulib\schema\types\tcontent;
|
||||
use nulib\schema\types\tpkey;
|
||||
use nulib\schema\types\tstring;
|
||||
use nulib\schema\Value;
|
||||
|
||||
/**
|
||||
* Class ScalarSchema
|
||||
*
|
||||
* @property-read array $type
|
||||
* @property-read mixed $default
|
||||
* @property-read string|null $title
|
||||
* @property-read bool $required
|
||||
* @property-read bool $nullable
|
||||
* @property-read string|array|null $desc
|
||||
* @property-read callable|null $analyzerFunc
|
||||
* @property-read callable|null $extractorFunc
|
||||
* @property-read callable|null $parserFunc
|
||||
* @property-read callable|null $normalizerFunc
|
||||
* @property-read array|null $messages
|
||||
* @property-read callable|null $formatterFunc
|
||||
* @property-read mixed $format
|
||||
* @property-read array $nature
|
||||
* @property-read string|int|null $name
|
||||
* @property-read string|array|null $pkey
|
||||
* @property-read string|null $header
|
||||
* @property-read bool|null $composite
|
||||
*/
|
||||
class ScalarSchema extends Schema {
|
||||
/** @var array meta-schema d'un schéma de nature scalaire */
|
||||
const METASCHEMA = ref_schema::SCALAR_METASCHEMA;
|
||||
|
||||
/**
|
||||
* indiquer si $definition est une définition de schéma scalaire que
|
||||
* {@link normalize()} pourrait normaliser
|
||||
*/
|
||||
static function isa_definition($definition): bool {
|
||||
# chaine ou null
|
||||
if ($definition === null) return true;
|
||||
if (is_string($definition)) return true;
|
||||
if (!is_array($definition)) return false;
|
||||
# nature explicitement spécifiée
|
||||
if (array_key_exists("", $definition)) {
|
||||
$nature = $definition[""];
|
||||
if ($nature === "scalar") return true;
|
||||
if (is_array($nature)
|
||||
&& array_key_exists(0, $nature)
|
||||
&& $nature[0] === "scalar") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
# un unique élément chaine à l'index 0
|
||||
$count = count($definition);
|
||||
$haveIndex0 = array_key_exists(0, $definition);
|
||||
if ($count == 1 && $haveIndex0
|
||||
&& ($definition[0] === null || is_string($definition[0]))) {
|
||||
return true;
|
||||
}
|
||||
# un élément à l'index 0, et d'autres éléments
|
||||
return $haveIndex0 && $count > 1;
|
||||
}
|
||||
|
||||
static function normalize($definition, $definitionKey=null): array {
|
||||
if (!is_array($definition)) $definition = [$definition];
|
||||
# s'assurer que toutes les clés existent avec leur valeur par défaut
|
||||
$index = 0;
|
||||
foreach (array_keys(self::METASCHEMA) as $key) {
|
||||
if (!array_key_exists($key, $definition)) {
|
||||
if (array_key_exists($index, $definition)) {
|
||||
$definition[$key] = $definition[$index];
|
||||
unset($definition[$index]);
|
||||
$index++;
|
||||
} else {
|
||||
$definition[$key] = self::METASCHEMA[$key][1];
|
||||
}
|
||||
}
|
||||
}
|
||||
# réordonner les clés numériques
|
||||
if (cl::have_num_keys($definition)) {
|
||||
$keys = array_keys($definition);
|
||||
$index = 0;
|
||||
foreach ($keys as $key) {
|
||||
if (!is_int($key)) continue;
|
||||
$definition[$index] = $definition[$key];
|
||||
unset($definition[$key]);
|
||||
$index++;
|
||||
}
|
||||
}
|
||||
# type
|
||||
$types = [];
|
||||
$deftype = $definition["type"];
|
||||
$nullable = $definition["nullable"];
|
||||
if ($deftype === null) {
|
||||
$types[] = null;
|
||||
$nullable = true;
|
||||
} else {
|
||||
if (!is_array($deftype)) {
|
||||
if (!is_string($deftype)) throw SchemaException::invalid_type($deftype);
|
||||
$deftype = explode("|", $deftype);
|
||||
}
|
||||
foreach ($deftype as $type) {
|
||||
if ($type === null || $type === "null") {
|
||||
$nullable = true;
|
||||
continue;
|
||||
}
|
||||
if (!is_string($type)) throw SchemaException::invalid_type($type);
|
||||
if (substr($type, 0, 1) == "?") {
|
||||
$type = substr($type, 1);
|
||||
$nullable = true;
|
||||
}
|
||||
if ($type === "") throw SchemaException::invalid_type($type);
|
||||
$type = cl::get(ref_types::ALIASES, $type, $type);
|
||||
$types = array_merge($types, explode("|", $type));
|
||||
}
|
||||
if (!$types) throw SchemaException::invalid_schema("scalar: type is required");
|
||||
$types = array_keys(array_fill_keys($types, true));
|
||||
}
|
||||
$definition["type"] = $types;
|
||||
$definition["nullable"] = $nullable;
|
||||
# nature
|
||||
$nature = $definition[""];
|
||||
tarray::ensure_array($nature);
|
||||
if (!array_key_exists(0, $nature) || $nature[0] !== "scalar") {
|
||||
throw SchemaException::invalid_schema("expected scalar nature");
|
||||
}
|
||||
$definition[""] = $nature;
|
||||
# name, pkey, header
|
||||
$name = $definition["name"];
|
||||
$pkey = $definition["pkey"];
|
||||
$header = $definition["header"];
|
||||
if ($name === null) $name = $definitionKey;
|
||||
tstring::ensure_nstring($name);
|
||||
tpkey::ensure_npkey($pkey);
|
||||
tstring::ensure_nstring($header);
|
||||
if ($pkey === null) $pkey = $name;
|
||||
if ($header === null) $header = $name;
|
||||
$definition["name"] = $name;
|
||||
$definition["pkey"] = $pkey;
|
||||
$definition["header"] = $header;
|
||||
# autres éléments
|
||||
tstring::ensure_nstring($definition["title"]);
|
||||
tbool::ensure_bool($definition["required"]);
|
||||
tbool::ensure_bool($definition["nullable"]);
|
||||
tcontent::ensure_ncontent($definition["desc"]);
|
||||
tcallable::ensure_ncallable($definition["analyzer_func"]);
|
||||
tcallable::ensure_ncallable($definition["extractor_func"]);
|
||||
tcallable::ensure_ncallable($definition["parser_func"]);
|
||||
tcallable::ensure_ncallable($definition["normalizer_func"]);
|
||||
tarray::ensure_narray($definition["messages"]);
|
||||
tcallable::ensure_ncallable($definition["formatter_func"]);
|
||||
tbool::ensure_nbool($definition["composite"]);
|
||||
return $definition;
|
||||
}
|
||||
|
||||
function __construct($definition=null, $definitionKey=null, bool $normalize=true) {
|
||||
if ($definition === null) $definition = static::SCHEMA;
|
||||
if ($normalize) $definition = self::normalize($definition, $definitionKey);
|
||||
$this->definition = $definition;
|
||||
}
|
||||
|
||||
function isScalar(?ScalarSchema &$scalar=null): bool {
|
||||
$scalar = $this;
|
||||
return true;
|
||||
}
|
||||
|
||||
function newValue(?Value &$destv=null, &$dest=null, $destKey=null): ScalarValue {
|
||||
if ($destv instanceof ScalarValue) return $destv->reset($dest, $destKey);
|
||||
else return ($destv = new ScalarValue($this, $dest, $destKey));
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# key & properties
|
||||
|
||||
const _PROPERTY_PKEYS = [
|
||||
"analyzerFunc" => "analyzer_func",
|
||||
"extractorFunc" => "extractor_func",
|
||||
"parserFunc" => "parser_func",
|
||||
"normalizerFunc" => "normalizer_func",
|
||||
"formatterFunc" => "formatter_func",
|
||||
"nature" => ["", 0],
|
||||
];
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
<?php
|
||||
namespace nulib\schema\_scalar;
|
||||
|
||||
use nulib\ref\schema\ref_analyze;
|
||||
use nulib\schema\input\Input;
|
||||
use nulib\schema\types;
|
||||
use nulib\schema\types\IType;
|
||||
use nulib\schema\Value;
|
||||
use nulib\ValueException;
|
||||
|
||||
class ScalarValue extends Value {
|
||||
function __construct(ScalarSchema $schema, &$dest=null, $destKey=null, bool $defaultVerifix=true, ?bool $defaultThrow=null) {
|
||||
if ($dest !== null && $defaultThrow = null) {
|
||||
# Si $dest est null, ne pas lancer d'exception, parce qu'on considère que
|
||||
# c'est une initialisation sans conséquences
|
||||
$defaultThrow = true;
|
||||
}
|
||||
$this->schema = $schema;
|
||||
$this->defaultVerifix = $defaultVerifix;
|
||||
$this->defaultThrow = $defaultThrow !== null? $defaultThrow: false;
|
||||
$this->result = new ScalarResult();
|
||||
$this->reset($dest, $destKey);
|
||||
$this->defaultThrow = $defaultThrow !== null? $defaultThrow: true;
|
||||
}
|
||||
|
||||
function isScalar(?ScalarValue &$scalar=null): bool { $scalar = $this; return true; }
|
||||
|
||||
/** @var ScalarSchema schéma de cette valeur */
|
||||
protected $schema;
|
||||
|
||||
/** @var Input source et destination de la valeur */
|
||||
protected $input;
|
||||
|
||||
/** @var string|int|null clé de la valeur dans le tableau destination */
|
||||
protected $destKey;
|
||||
|
||||
/** @var bool */
|
||||
protected $defaultVerifix;
|
||||
|
||||
/** @var bool */
|
||||
protected $defaultThrow;
|
||||
|
||||
/** @var IType type de la valeur après analyse */
|
||||
protected $type;
|
||||
|
||||
/** @var ?ScalarResult résultat de l'analyse de la valeur */
|
||||
protected $result;
|
||||
|
||||
function reset(&$dest, $destKey=null, ?bool $verifix=null): Value {
|
||||
if ($dest instanceof Input) $input = $dest;
|
||||
else $input = new Input($dest);
|
||||
$this->input = $input;
|
||||
$this->destKey = $destKey;
|
||||
$this->type = null;
|
||||
$this->_analyze();
|
||||
if ($verifix == null) $verifix = $this->defaultVerifix;
|
||||
if ($verifix) $this->verifix();
|
||||
return $this;
|
||||
}
|
||||
|
||||
function getKeys(): array {
|
||||
return [null];
|
||||
}
|
||||
|
||||
function getValue($key=null): ScalarValue {
|
||||
if ($key === null) return $this;
|
||||
throw ValueException::invalid_key($key);
|
||||
}
|
||||
|
||||
/** analyser la valeur et résoudre son type */
|
||||
function _analyze(): int {
|
||||
$schema = $this->schema;
|
||||
$input = $this->input;
|
||||
$destKey = $this->destKey;
|
||||
$result = $this->result;
|
||||
$result->reset();
|
||||
if (!$input->isPresent($destKey)) return $result->setMissing($schema);
|
||||
$haveType = false;
|
||||
$types = [];
|
||||
$type = $firstType = null;
|
||||
$haveValue = false;
|
||||
$value = null;
|
||||
# d'abord chercher un type pour lequel c'est une valeur normalisée
|
||||
foreach ($schema->type as $name) {
|
||||
$type = types::get($name);
|
||||
if ($firstType === null) $firstType = $type;
|
||||
$types[] = $type;
|
||||
if ($type->isAvailable($input, $destKey)) {
|
||||
if (!$haveValue) {
|
||||
$value = $input->get($destKey);
|
||||
$haveValue = true;
|
||||
}
|
||||
if ($type->isValid($value, $normalized) && $normalized) {
|
||||
$haveType = true;
|
||||
$this->type = $type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$haveType) {
|
||||
# ensuite chercher un type pour lequel la valeur est valide
|
||||
foreach ($types as $type) {
|
||||
if ($type->isAvailable($input, $destKey) && $type->isValid($value)) {
|
||||
$haveType = true;
|
||||
$this->type = $type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
# sinon prendre le premier type
|
||||
if (!$haveType) $type = $this->type = $firstType;
|
||||
if (!$type->isAvailable($input, $destKey)) return $result->setUnavailable($schema);
|
||||
$value = $input->get($destKey);
|
||||
if ($type->isNull($value)) return $result->setNull($schema);
|
||||
if ($type->isValid($value, $normalized)) {
|
||||
if ($normalized) return $result->setNormalized();
|
||||
else return $result->setValid();
|
||||
}
|
||||
if (is_string($value)) return ref_analyze::STRING;
|
||||
else return $result->setInvalid($value, $schema);
|
||||
}
|
||||
|
||||
function verifix(?bool $throw=null): bool {
|
||||
if ($throw === null) $throw = $this->defaultThrow;
|
||||
$destKey = $this->destKey;
|
||||
$verifix = false;
|
||||
$result =& $this->result;
|
||||
$modified = false;
|
||||
if ($result->resultAvailable) {
|
||||
if ($result->null) {
|
||||
# forcer la valeur null, parce que la valeur actuelle est peut-être une
|
||||
# valeur assimilée à null
|
||||
$this->input->set(null, $destKey);
|
||||
} elseif ($result->valid && !$result->normalized) {
|
||||
# normaliser la valeur
|
||||
$verifix = true;
|
||||
}
|
||||
} else {
|
||||
$verifix = true;
|
||||
}
|
||||
if ($verifix) {
|
||||
$value = $this->input->get($destKey);
|
||||
$modified = $this->type->verifix($value, $result, $this->schema);
|
||||
if ($result->valid) $this->input->set($value, $destKey);
|
||||
}
|
||||
if (!$result->valid) $result->throw($throw);
|
||||
return $modified;
|
||||
}
|
||||
|
||||
function getResult(): ScalarResult {
|
||||
return $this->result;
|
||||
}
|
||||
|
||||
function isPresent(): bool {
|
||||
return $this->result->present;
|
||||
}
|
||||
|
||||
function getType(): IType {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
function isAvailable(): bool {
|
||||
return $this->result->available;
|
||||
}
|
||||
|
||||
function isValid(): bool {
|
||||
return $this->result->valid;
|
||||
}
|
||||
|
||||
function isNormalized(): bool {
|
||||
return $this->result->normalized;
|
||||
}
|
||||
|
||||
function get($default=null) {
|
||||
if ($this->result->available) return $this->input->get($this->destKey);
|
||||
else return $default;
|
||||
}
|
||||
|
||||
function set($value, ?bool $verifix=null): ScalarValue {
|
||||
$this->input->set($value, $this->destKey);
|
||||
$this->_analyze();
|
||||
if ($verifix === null) $verifix = $this->defaultVerifix;
|
||||
if ($verifix) $this->verifix();
|
||||
return $this;
|
||||
}
|
||||
|
||||
function unset(?bool $verifix=null): ScalarValue {
|
||||
$this->input->unset($this->destKey);
|
||||
$this->_analyze();
|
||||
if ($verifix === null) $verifix = $this->defaultVerifix;
|
||||
if ($verifix) $this->verifix();
|
||||
return $this;
|
||||
}
|
||||
|
||||
function format($format=null): string {
|
||||
return $this->type->format($this->input->get($this->destKey), $format);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
namespace nulib\schema\input;
|
||||
|
||||
#XXX implémenter le renommage de paramètres et faire des méthodes pour
|
||||
# construire des querystring et paramètres de formulaires
|
||||
/**
|
||||
* Class FormInput: accès à des paramètres de formulaire (POST ou GET, dans cet
|
||||
* ordre)
|
||||
*
|
||||
* cette implémentation lit depuis les paramètres de formulaire et écrit dans
|
||||
* une référence
|
||||
*/
|
||||
class FormInput extends Input {
|
||||
const ALLOW_EMPTY = false;
|
||||
|
||||
function isPresent($key=null): bool {
|
||||
if ($key === null) return false;
|
||||
return array_key_exists($key, $_POST) || array_key_exists($key, $_GET);
|
||||
}
|
||||
|
||||
function isAvailable($key=null): bool {
|
||||
if ($key === null) return false;
|
||||
if (array_key_exists($key, $_POST)) {
|
||||
return $this->allowEmpty || $_POST[$key] !== "";
|
||||
} elseif (array_key_exists($key, $_GET)) {
|
||||
return $this->allowEmpty || $_GET[$key] !== "";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function get($key=null) {
|
||||
if ($key === null) return null;
|
||||
if (array_key_exists($key, $_POST)) {
|
||||
$value = $_POST[$key];
|
||||
if ($value === "" && !$this->allowEmpty) return null;
|
||||
return $value;
|
||||
} elseif (array_key_exists($key, $_GET)) {
|
||||
$value = $_GET[$key];
|
||||
if ($value === "" && !$this->allowEmpty) return null;
|
||||
return $value;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
namespace nulib\schema\input;
|
||||
|
||||
/**
|
||||
* Class GetInput: accès à des paramètres de formulaire de type GET uniquement
|
||||
*
|
||||
* cette implémentation lit depuis les paramètres de formulaire et écrit dans
|
||||
* une référence
|
||||
*/
|
||||
class GetInput extends FormInput {
|
||||
function isPresent($key=null): bool {
|
||||
if ($key === null) return false;
|
||||
return array_key_exists($key, $_GET);
|
||||
}
|
||||
|
||||
function isAvailable($key=null): bool {
|
||||
if ($key === null) return false;
|
||||
if (array_key_exists($key, $_GET)) {
|
||||
return $this->allowEmpty || $_GET[$key] !== "";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function get($key=null) {
|
||||
if ($key === null) return null;
|
||||
if (array_key_exists($key, $_GET)) {
|
||||
$value = $_GET[$key];
|
||||
if ($value === "" && !$this->allowEmpty) return null;
|
||||
return $value;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
namespace nulib\schema\input;
|
||||
|
||||
use nulib\cl;
|
||||
|
||||
/**
|
||||
* Class Input: accès à une valeur
|
||||
*
|
||||
* cette implémentation lit depuis et écrit dans une référence
|
||||
*/
|
||||
class Input {
|
||||
const ALLOW_EMPTY = true;
|
||||
|
||||
function __construct(&$dest=null, ?array $params=null) {
|
||||
$this->dest =& $dest;
|
||||
$allowEmpty = cl::get($params, "allow_empty");
|
||||
if ($allowEmpty === null) $allowEmpty = static::ALLOW_EMPTY;
|
||||
$this->allowEmpty = $allowEmpty;
|
||||
}
|
||||
|
||||
protected $dest;
|
||||
|
||||
/** tester si la valeur existe sans tenir compte de $allowEmpty */
|
||||
function isPresent($key=null): bool {
|
||||
if ($key === null) return true;
|
||||
$dest = $this->dest;
|
||||
return $dest !== null && array_key_exists($key, $dest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var bool comment considérer une chaine vide: "" si allowEmpty, null sinon
|
||||
*/
|
||||
protected $allowEmpty;
|
||||
|
||||
/** tester si la valeur est disponible en tenant compte de $allowEmpty */
|
||||
function isAvailable($key=null): bool {
|
||||
if ($key === null) return true;
|
||||
$dest = $this->dest;
|
||||
if ($dest === null || !array_key_exists($key, $dest)) return false;
|
||||
return $this->allowEmpty || $dest[$key] !== "";
|
||||
}
|
||||
|
||||
function get($key=null) {
|
||||
$dest = $this->dest;
|
||||
if ($key === null) return $dest;
|
||||
if ($dest === null || !array_key_exists($key, $dest)) return null;
|
||||
$value = $dest[$key];
|
||||
if ($value === "" && !$this->allowEmpty) return null;
|
||||
return $value;
|
||||
}
|
||||
|
||||
function set($value, $key=null): void {
|
||||
if ($key === null) $this->dest = $value;
|
||||
else $this->dest[$key] = $value;
|
||||
}
|
||||
|
||||
function unset($key=null): void {
|
||||
if ($key === null) $this->dest = null;
|
||||
else unset($this->dest[$key]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
namespace nulib\schema\input;
|
||||
|
||||
/**
|
||||
* Class PostInput: accès à des paramètres de formulaire de type POST uniquement
|
||||
*
|
||||
* cette implémentation lit depuis les paramètres de formulaire et écrit dans
|
||||
* une référence
|
||||
*/
|
||||
class PostInput extends FormInput {
|
||||
function isPresent($key=null): bool {
|
||||
if ($key === null) return false;
|
||||
return array_key_exists($key, $_POST);
|
||||
}
|
||||
|
||||
function isAvailable($key=null): bool {
|
||||
if ($key === null) return false;
|
||||
if (array_key_exists($key, $_POST)) {
|
||||
return $this->allowEmpty || $_POST[$key] !== "";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function get($key=null) {
|
||||
if ($key === null) return null;
|
||||
if (array_key_exists($key, $_POST)) {
|
||||
$value = $_POST[$key];
|
||||
if ($value === "" && !$this->allowEmpty) return null;
|
||||
return $value;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
namespace nulib\schema;
|
||||
|
||||
use nulib\schema\types\IType;
|
||||
use nulib\schema\types\Registry;
|
||||
use nulib\schema\types\tarray;
|
||||
use nulib\schema\types\tbool;
|
||||
use nulib\schema\types\tcallable;
|
||||
use nulib\schema\types\tfloat;
|
||||
use nulib\schema\types\tint;
|
||||
use nulib\schema\types\tstring;
|
||||
|
||||
/**
|
||||
* Class types: classe outil pour gérer le registre de types
|
||||
*/
|
||||
class types {
|
||||
/** @var Registry */
|
||||
private static $registry;
|
||||
|
||||
static function registry(): Registry {
|
||||
if (self::$registry === null) {
|
||||
self::$registry = new Registry();
|
||||
}
|
||||
return self::$registry;
|
||||
}
|
||||
|
||||
static function get(string $name): IType {
|
||||
return self::registry()->get($name);
|
||||
}
|
||||
|
||||
static function string(): tstring { return self::get("string"); }
|
||||
static function bool(): tbool { return self::get("bool"); }
|
||||
static function int(): tint { return self::get("int"); }
|
||||
static function float(): tfloat { return self::get("float"); }
|
||||
static function array(): tarray { return self::get("array"); }
|
||||
static function callable(): tcallable { return self::get("callable"); }
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nulib\schema\input\Input;
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
|
||||
/**
|
||||
* Interface IType: un type de données
|
||||
*/
|
||||
interface IType {
|
||||
/** la donnée $input($destKey) est-elle disponible? */
|
||||
function isAvailable(Input $input, $destKey): bool;
|
||||
|
||||
/** la valeur $value est-elle nulle? */
|
||||
function isNull($value): bool;
|
||||
|
||||
/** la valeur $value est-elle valide et normalisée le cas échéant? */
|
||||
function isValid($value, ?bool &$normalized=null): bool;
|
||||
|
||||
/**
|
||||
* analyser, corriger éventuellement et normaliser la valeur
|
||||
*
|
||||
* si la valeur était déjà normalisée, ou si une erreur s'est produite,
|
||||
* retourner false.
|
||||
*/
|
||||
function verifix(&$value, Result &$result, Schema $schema): bool;
|
||||
|
||||
/**
|
||||
* formatter la valeur pour affichage. $value est garanti d'être du bon type
|
||||
*/
|
||||
function format($value, $format=null): string;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nulib\cl;
|
||||
|
||||
class Registry {
|
||||
const TYPES = [
|
||||
"string" => tstring::class,
|
||||
"bool" => tbool::class, "boolean" => tbool::class,
|
||||
"int" => tint::class, "integer" => tint::class,
|
||||
"float" => tfloat::class, "flt" => tfloat::class,
|
||||
"double" => tfloat::class, "dbl" => tfloat::class,
|
||||
"array" => tarray::class,
|
||||
"callable" => tcallable::class,
|
||||
# types spéciaux
|
||||
"key" => tkey::class,
|
||||
"pkey" => tpkey::class,
|
||||
"content" => tcontent::class,
|
||||
];
|
||||
|
||||
function __construct() {
|
||||
$this->types = [];
|
||||
}
|
||||
|
||||
/** @var IType[] */
|
||||
protected $types;
|
||||
|
||||
function get(string $name): IType {
|
||||
$type = cl::get($this->types, $name);
|
||||
if ($type === null) {
|
||||
$class = self::TYPES[$name];
|
||||
$type = $this->types[$name] = new $class();
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nulib\schema\input\Input;
|
||||
|
||||
abstract class _tsimple implements IType {
|
||||
function isAvailable(Input $input, $destKey): bool {
|
||||
return $input->isAvailable($destKey) && $input->get($destKey) !== false;
|
||||
}
|
||||
|
||||
function isNull($value): bool {
|
||||
return $value === null || (is_string($value) && trim($value) === "");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
|
||||
class tarray extends _tsimple {
|
||||
static function ensure_array(&$array): void {
|
||||
if (!is_array($array)) $array = cl::with($array);
|
||||
}
|
||||
|
||||
static function ensure_narray(&$array): void {
|
||||
if ($array !== null) self::ensure_array($array);
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = is_array($value);
|
||||
return is_scalar($value) || is_array($value);
|
||||
}
|
||||
|
||||
function verifix(&$value, Result &$result, Schema $schema): bool {
|
||||
}
|
||||
|
||||
function format($value, $format=null): string {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\schema\_scalar\ScalarResult;
|
||||
use nulib\schema\_scalar\ScalarSchema;
|
||||
use nulib\schema\input\Input;
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
use nulib\ValueException;
|
||||
|
||||
class tbool extends _tsimple {
|
||||
/** liste de valeurs chaines à considérer comme 'OUI' */
|
||||
const YES_VALUES = [
|
||||
# IMPORTANT: ordonner par taille décroissante pour compatibilité avec parse()
|
||||
"true", "vrai", "yes", "oui",
|
||||
"t", "v", "y", "o", "1",
|
||||
];
|
||||
|
||||
/** liste de valeurs chaines à considérer comme 'NON' */
|
||||
const NO_VALUES = [
|
||||
# IMPORTANT: ordonner par taille décroissante pour compatibilité avec parse()
|
||||
"false", "faux", "non", "no",
|
||||
"f", "n", "0",
|
||||
];
|
||||
|
||||
/** Vérifier si $value est 'OUI' */
|
||||
static final function is_yes(?string $value): bool {
|
||||
if ($value === null) return false;
|
||||
$value = strtolower(trim($value));
|
||||
if (in_array($value, self::YES_VALUES, true)) return true;
|
||||
// n'importe quelle valeur numérique
|
||||
if (is_numeric($value)) return $value != 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Vérifier si $value est 'NON' */
|
||||
static final function is_no(?string $value): bool {
|
||||
if ($value === null) return true;
|
||||
$value = strtolower(trim($value));
|
||||
return in_array($value, self::NO_VALUES, true);
|
||||
}
|
||||
|
||||
static function ensure_bool(&$bool): void {
|
||||
if (is_string($bool)) {
|
||||
if (self::is_yes($bool)) $bool = true;
|
||||
elseif (self::is_no($bool)) $bool = false;
|
||||
}
|
||||
if (!is_bool($bool)) $bool = boolval($bool);
|
||||
}
|
||||
|
||||
static function ensure_nbool(&$bool): void {
|
||||
if ($bool !== null) self::ensure_bool($bool);
|
||||
}
|
||||
|
||||
function isAvailable(Input $input, $destKey): bool {
|
||||
return $input->isAvailable($destKey);
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = is_bool($value);
|
||||
if (is_string($value)) {
|
||||
$value = trim($value);
|
||||
$valid = self::is_yes($value) || self::is_no($value);
|
||||
} else {
|
||||
$valid = is_scalar($value);
|
||||
}
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ScalarResult $result
|
||||
* @var ScalarSchema $schema
|
||||
*/
|
||||
function verifix(&$value, Result &$result, Schema $schema): bool {
|
||||
if (is_bool($value)) {
|
||||
$result->setNormalized();
|
||||
return false;
|
||||
} elseif (is_string($value)) {
|
||||
$bool = trim($value);
|
||||
if (self::is_yes($bool)) $value = true;
|
||||
elseif (self::is_no($bool)) $value = false;
|
||||
else return $result->setInvalid($value, $schema);
|
||||
} elseif (is_scalar($value)) {
|
||||
$value = boolval($value);
|
||||
} else {
|
||||
return $result->setInvalid($value, $schema);
|
||||
}
|
||||
$result->setValid();
|
||||
return true;
|
||||
}
|
||||
|
||||
const OUINON_FORMAT = ["Oui", "Non", false];
|
||||
const OUINONNULL_FORMAT = ["Oui", "Non", ""];
|
||||
const ON_FORMAT = ["O", "N", false];
|
||||
const ONN_FORMAT = ["O", "N", ""];
|
||||
const XN_FORMAT = ["X", "", false];
|
||||
const OZ_FORMAT = ["1", "", false];
|
||||
const FORMATS = [
|
||||
"ouinon" => self::OUINON_FORMAT,
|
||||
"ouinonnull" => self::OUINONNULL_FORMAT,
|
||||
"on" => self::ON_FORMAT,
|
||||
"onn" => self::ONN_FORMAT,
|
||||
"xn" => self::XN_FORMAT,
|
||||
"oz" => self::OZ_FORMAT,
|
||||
];
|
||||
|
||||
const DEFAULT_FORMAT = self::OUINON_FORMAT;
|
||||
|
||||
function format($value, $format=null): string {
|
||||
if ($format === null) $format = static::DEFAULT_FORMAT;
|
||||
if (is_string($format)) {
|
||||
$oformat = $format;
|
||||
$format = cl::get(self::FORMATS, strtolower($oformat));
|
||||
if ($format === null) throw ValueException::invalid_kind($oformat, "format");
|
||||
}
|
||||
if ($value === null) {
|
||||
$null = $format[2];
|
||||
if ($null !== false) return $null;
|
||||
}
|
||||
return $value? $format[0]: $format[1];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nulib\php\func;
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
use nulib\ValueException;
|
||||
use stdClass;
|
||||
|
||||
class tcallable extends _tsimple {
|
||||
static function ensure_callable(&$callable): void {
|
||||
if (!is_callable($callable)) throw ValueException::invalid_type($callable, "callable");
|
||||
}
|
||||
|
||||
static function ensure_ncallable(&$callable): void {
|
||||
if ($callable !== null) self::ensure_callable($callable);
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = is_callable($value);
|
||||
return func::check_func($value, stdClass::class);
|
||||
}
|
||||
|
||||
function verifix(&$value, Result &$result, Schema $schema): bool {
|
||||
}
|
||||
|
||||
function format($value, $format=null): string {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
|
||||
abstract class tcontent extends _tsimple {
|
||||
static function ensure_content(&$content): void {
|
||||
if (!is_string($content) && !is_array($content)) $content = strval($content);
|
||||
}
|
||||
|
||||
static function ensure_ncontent(&$content): void {
|
||||
if ($content !== null) self::ensure_content($content);
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = is_string($value) || is_array($value);
|
||||
return is_scalar($value) || is_array($value);
|
||||
}
|
||||
|
||||
function verifix(&$value, Result &$result, Schema $schema): bool {
|
||||
}
|
||||
|
||||
function format($value, $format=null): string {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nulib\schema\_scalar\ScalarResult;
|
||||
use nulib\schema\_scalar\ScalarSchema;
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
|
||||
class tfloat extends _tsimple {
|
||||
static function ensure_float(&$float): void {
|
||||
if (!is_float($float)) $float = floatval($float);
|
||||
}
|
||||
|
||||
static function ensure_nfloat(&$float): void {
|
||||
if ($float !== null) self::ensure_float($float);
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = is_float($value);
|
||||
if (is_string($value)) $valid = is_numeric(trim($value));
|
||||
else $valid = is_scalar($value);
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ScalarResult $result
|
||||
* @var ScalarSchema $schema
|
||||
*/
|
||||
function verifix(&$value, Result &$result, Schema $schema): bool {
|
||||
if (is_float($value)) {
|
||||
$result->setNormalized();
|
||||
return false;
|
||||
} elseif (is_string($value)) {
|
||||
$float = trim($value);
|
||||
if (is_numeric($float)) $value = floatval($float);
|
||||
else return $result->setInvalid($value, $schema);
|
||||
} elseif (is_scalar($value)) {
|
||||
$value = floatval($value);
|
||||
} else {
|
||||
return $result->setInvalid($value, $schema);
|
||||
}
|
||||
$result->setValid();
|
||||
return true;
|
||||
}
|
||||
|
||||
function format($value, $format=null): string {
|
||||
if ($format !== null) return sprintf($format, $value);
|
||||
else return strval($value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nulib\schema\_scalar\ScalarResult;
|
||||
use nulib\schema\_scalar\ScalarSchema;
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
|
||||
class tint extends _tsimple {
|
||||
static function ensure_int(&$int): void {
|
||||
if (!is_int($int)) $int = intval($int);
|
||||
}
|
||||
|
||||
static function ensure_nint(&$int): void {
|
||||
if ($int !== null) self::ensure_int($int);
|
||||
}
|
||||
|
||||
const INT_PATTERN = '/^[-+]?[0-9]+(?:\.[0-9]*)?$/';
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = is_int($value);
|
||||
if (is_string($value)) $valid = is_numeric(trim($value));
|
||||
else $valid = is_scalar($value);
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ScalarResult $result
|
||||
* @var ScalarSchema $schema
|
||||
*/
|
||||
function verifix(&$value, Result &$result, Schema $schema): bool {
|
||||
if (is_int($value)) {
|
||||
$result->setNormalized();
|
||||
return false;
|
||||
} elseif (is_string($value)) {
|
||||
$int = trim($value);
|
||||
if (is_numeric($int)) $value = intval($int);
|
||||
else return $result->setInvalid($value, $schema);
|
||||
} elseif (is_scalar($value)) {
|
||||
$value = intval($value);
|
||||
} else {
|
||||
return $result->setInvalid($value, $schema);
|
||||
}
|
||||
$result->setValid();
|
||||
return true;
|
||||
}
|
||||
|
||||
function format($value, $format=null): string {
|
||||
if ($format !== null) return sprintf($format, $value);
|
||||
else return strval($value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
|
||||
class tkey extends _tsimple {
|
||||
static function ensure_key(&$key): void {
|
||||
if (!is_string($key) && !is_int($key)) $key = strval($key);
|
||||
}
|
||||
|
||||
static function ensure_nkey(&$key): void {
|
||||
if ($key !== null) self::ensure_key($key);
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = is_string($value) || is_int($value);
|
||||
return is_scalar($value);
|
||||
}
|
||||
|
||||
function verifix(&$value, Result &$result, Schema $schema): bool {
|
||||
}
|
||||
|
||||
function format($value, $format=null): string {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
|
||||
class tpkey extends _tsimple {
|
||||
static function ensure_pkey(&$pkey): void {
|
||||
if (!is_string($pkey) && !is_int($pkey) && !is_array($pkey)) $pkey = strval($pkey);
|
||||
if (is_array($pkey)) {
|
||||
foreach ($pkey as &$key) {
|
||||
tkey::ensure_key($key);
|
||||
};
|
||||
unset($key);
|
||||
}
|
||||
}
|
||||
|
||||
static function ensure_npkey(&$pkey): void {
|
||||
if ($pkey !== null) self::ensure_pkey($pkey);
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = is_string($value) || is_int($value) || is_array($value);
|
||||
return is_scalar($value) || is_array($value);
|
||||
}
|
||||
|
||||
function verifix(&$value, Result &$result, Schema $schema): bool {
|
||||
}
|
||||
|
||||
function format($value, $format=null): string {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nulib\schema\_scalar\ScalarResult;
|
||||
use nulib\schema\_scalar\ScalarSchema;
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
|
||||
class tstring extends _tsimple {
|
||||
static function ensure_string(&$string): void {
|
||||
if (!is_string($string)) $string = strval($string);
|
||||
}
|
||||
|
||||
static function ensure_nstring(&$string): void {
|
||||
if ($string !== null) self::ensure_string($string);
|
||||
}
|
||||
|
||||
function isNull($value): bool {
|
||||
return $value === null;
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = is_string($value);
|
||||
return is_scalar($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ScalarResult $result
|
||||
* @var ScalarSchema $schema
|
||||
*/
|
||||
function verifix(&$value, Result &$result, Schema $schema): bool {
|
||||
if (is_string($value)) {
|
||||
$result->setNormalized();
|
||||
return false;
|
||||
} elseif (is_scalar($value)) {
|
||||
$value = strval($value);
|
||||
$result->setValid();
|
||||
return true;
|
||||
} else {
|
||||
$result->setInvalid($value, $schema);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function format($value, $format=null): string {
|
||||
return strval($value);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
<?php
|
||||
namespace nulib;
|
||||
|
||||
use nur\base;
|
||||
|
||||
/**
|
||||
* Class str: gestion des chaines de caractère "simples"
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,212 @@
|
|||
<?php
|
||||
namespace nulib\text;
|
||||
|
||||
use nur\b\ValueException;
|
||||
use nur\txt;
|
||||
|
||||
/**
|
||||
* Class Word: accord d'un nom ou d'un adjectif en genre et en nombre
|
||||
*
|
||||
* Pour accorder un nom, construire l'objet avec une spécification de la forme
|
||||
* "GENRE:ARTICLE NOM"
|
||||
* - L'article peut être "l'", "le", "la". Le genre est requis avec "l'"
|
||||
* - Le genre peut être "masculin:" ou "féminin:"
|
||||
* - Le nom est composé d'un ou plusieurs mots qui se terminent par
|
||||
* - #s pour un pluriel en "s", e.g porte#s
|
||||
* - #x pour un pluriel en "x", e.g lieu#x
|
||||
* - rien si le mot est invariable
|
||||
*
|
||||
* Pour accorder un adjectif, la spécification peut se limiter à "ADJECTIF"
|
||||
* - L'adjectif est composé d'un ou plusieurs mots, qui en plus des marques du
|
||||
* pluriel peuvent se terminent par
|
||||
* - #e pour indiquer la marque du féminin, e.g "né#e"
|
||||
* - rien si le mot est invariable
|
||||
*
|
||||
* Chaque mot peut aussi commencer par "^" pour indiquer les caractères qui
|
||||
* peuvent être mis en majuscule par la méthode u(). Par défaut, seule la
|
||||
* première lettre est mise en majuscule
|
||||
*/
|
||||
class Word {
|
||||
/** @var bool le mot est-il féminin? */
|
||||
private $fem;
|
||||
/** @var string article "le", "la", "l'" */
|
||||
private $le;
|
||||
/** @var string article "du", "de la", "de l'" */
|
||||
private $du;
|
||||
/** @var string article "au", "à la", "à l'" */
|
||||
private $au;
|
||||
/** @var string le mot sans article */
|
||||
private $w;
|
||||
|
||||
function __construct(string $spec, bool $adjective=false) {
|
||||
if (preg_match('/^f([eé]m(inin)?)?\s*:\s*/iu', $spec, $ms)) {
|
||||
$fem = true;
|
||||
$spec = substr($spec, strlen($ms[0]));
|
||||
} elseif (preg_match('/^m(asc(ulin)?)?\s*:\s*/i', $spec, $ms)) {
|
||||
$fem = false;
|
||||
$spec = substr($spec, $ms[0]);
|
||||
} elseif (preg_match('/\s*\|f(?:[eé]m(?:inin)?)?\s*$/iu', $spec, $ms, PREG_OFFSET_CAPTURE)) {
|
||||
$fem = true;
|
||||
$spec = substr($spec, 0, $ms[0][1]);
|
||||
} elseif (preg_match('/\s*\|m(?:asc(?:ulin)?)?\s*$/i', $spec, $ms, PREG_OFFSET_CAPTURE)) {
|
||||
$fem = false;
|
||||
$spec = substr($spec, 0, $ms[0][1]);
|
||||
} else {
|
||||
$fem = null;
|
||||
}
|
||||
if (preg_match('/^l\'\s*/i', $spec, $ms) && $fem !== null) {
|
||||
$le = "l'";
|
||||
$du = "de l'";
|
||||
$au = "à l'";
|
||||
$spec = substr($spec, strlen($ms[0]));
|
||||
} elseif (preg_match('/^la\s+/i', $spec, $ms)) {
|
||||
$fem = true;
|
||||
$le = "la ";
|
||||
$du = "de la ";
|
||||
$au = "à la ";
|
||||
$spec = substr($spec, strlen($ms[0]));
|
||||
} elseif (preg_match('/^le\s+/i', $spec, $ms)) {
|
||||
$fem = false;
|
||||
$le = "le ";
|
||||
$du = "du ";
|
||||
$au = "au ";
|
||||
$spec = substr($spec, strlen($ms[0]));
|
||||
} else {
|
||||
$le = null;
|
||||
$du = null;
|
||||
$au = null;
|
||||
}
|
||||
if (!$adjective) {
|
||||
# si c'est un nom, il faut l'article et le genre
|
||||
if ($fem === null) {
|
||||
throw new ValueException("Vous devez spécifier le genre du nom");
|
||||
} elseif ($le === null || $du === null || $au === null) {
|
||||
throw new ValueException("Vous devez spécifier l'article du nom");
|
||||
}
|
||||
}
|
||||
$this->fem = $fem;
|
||||
$this->le = $le;
|
||||
$this->du = $du;
|
||||
$this->au = $au;
|
||||
$this->w = $spec;
|
||||
}
|
||||
|
||||
/**
|
||||
* retourner le mot sans article
|
||||
*
|
||||
* @param bool|int $amount nombre du nom, avec l'équivalence false===0 et
|
||||
* true===2. à partir de 2, le mot est ecrit au pluriel
|
||||
* @param bool|string $fem genre du nom avec lequel accorder les adjectifs,
|
||||
* avec l'équivalence false==="M" et true==="F"
|
||||
*/
|
||||
function w($amount=1, bool $upper1=false, $fem=false): string {
|
||||
if ($amount === true) $amount = 2;
|
||||
elseif ($amount === false) $amount = 0;
|
||||
$amount = abs($amount);
|
||||
$w = $this->w;
|
||||
# marque du nombre
|
||||
if ($amount <= 1) {
|
||||
$w = preg_replace('/#[sx]/', "", $w);
|
||||
} else {
|
||||
$w = preg_replace('/#([sx])/', "$1", $w);
|
||||
}
|
||||
# marque du genre
|
||||
if ($fem === "f" || $fem === "F") $fem = true;
|
||||
elseif ($fem === "m" || $fem === "M") $fem = false;
|
||||
$repl = $fem? "$1": "";
|
||||
$w = preg_replace('/#([e])/', $repl, $w);
|
||||
# mise en majuscule
|
||||
if ($upper1) {
|
||||
if (strpos($w, "^") === false) {
|
||||
# uniquement la première lettre
|
||||
$w = txt::upper1($w);
|
||||
} else {
|
||||
# toutes les lettres qui suivent les occurences de ^
|
||||
$w = preg_replace_callback('/\^([[:alpha:]])/u', function ($ms) {
|
||||
return mb_strtoupper($ms[1]);
|
||||
}, $w);
|
||||
}
|
||||
}
|
||||
return $w;
|
||||
}
|
||||
|
||||
/**
|
||||
* retourner le mot sans article avec la première lettre en majuscule.
|
||||
* alias pour $this->w($amount, true, $fem)
|
||||
*
|
||||
* @param bool|int $amount
|
||||
*/
|
||||
function u($amount=1, $fem=false): string {
|
||||
return $this->w($amount, true, $fem);
|
||||
}
|
||||
|
||||
/**
|
||||
* retourner l'adjectif accordé avec le genre spécifié.
|
||||
* alias pour $this->w($amount, false, $fem)
|
||||
*
|
||||
* @param bool|int $amount
|
||||
*/
|
||||
function a($fem=false, $amount=1): string {
|
||||
return $this->w($amount, false, $fem);
|
||||
}
|
||||
|
||||
/** retourner le mot sans article et avec la quantité */
|
||||
function q(int $amount=1, $fem=false): string {
|
||||
return $amount." ".$this->w($amount, $fem);
|
||||
}
|
||||
|
||||
/** retourner le mot sans article et avec la quantité $amount/$max */
|
||||
function r(int $amount, int $max, $fem=false): string {
|
||||
return "$amount/$max ".$this->w($amount, $fem);
|
||||
}
|
||||
|
||||
/** retourner le mot avec l'article indéfini et la quantité */
|
||||
function un(int $amount=1, $fem=false): string {
|
||||
$abs_amount = abs($amount);
|
||||
if ($abs_amount == 0) {
|
||||
$aucun = $this->fem? "aucune ": "aucun ";
|
||||
return $aucun.$this->w($amount, $fem);
|
||||
} elseif ($abs_amount == 1) {
|
||||
$un = $this->fem? "une ": "un ";
|
||||
return $un.$this->w($amount, $fem);
|
||||
} else {
|
||||
return "les $amount ".$this->w($amount, $fem);
|
||||
}
|
||||
}
|
||||
|
||||
function le(int $amount=1, $fem=false): string {
|
||||
$abs_amount = abs($amount);
|
||||
if ($abs_amount == 0) {
|
||||
$le = $this->fem? "la 0 ": "le 0 ";
|
||||
return $le.$this->w($amount, $fem);
|
||||
} elseif ($abs_amount == 1) {
|
||||
return $this->le.$this->w($amount, $fem);
|
||||
} else {
|
||||
return "les $amount ".$this->w($amount, $fem);
|
||||
}
|
||||
}
|
||||
|
||||
function du(int $amount=1, $fem=false): string {
|
||||
$abs_amount = abs($amount);
|
||||
if ($abs_amount == 0) {
|
||||
$du = $this->fem? "de la 0 ": "du 0 ";
|
||||
return $du.$this->w($amount, $fem);
|
||||
} elseif ($abs_amount == 1) {
|
||||
return $this->du.$this->w($amount, $fem);
|
||||
} else {
|
||||
return "des $amount ".$this->w($amount, $fem);
|
||||
}
|
||||
}
|
||||
|
||||
function au(int $amount=1, $fem=false): string {
|
||||
$abs_amount = abs($amount);
|
||||
if ($abs_amount == 0) {
|
||||
$au = $this->fem? "à la 0 ": "au 0 ";
|
||||
return $au.$this->w($amount, $fem);
|
||||
} elseif ($abs_amount == 1) {
|
||||
return $this->au.$this->w($amount, $fem);
|
||||
} else {
|
||||
return "aux $amount ".$this->w($amount, $fem);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
namespace nulib\text;
|
||||
|
||||
class words {
|
||||
static function q(int $count, string $spec, bool $adjective=true): string {
|
||||
$word = new Word($spec, $adjective);
|
||||
return $word->q($count);
|
||||
}
|
||||
|
||||
static function r(int $count, int $max, string $spec, bool $adjective=true): string {
|
||||
$word = new Word($spec, $adjective);
|
||||
return $word->r($count, $max);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace nulib\web\ext;
|
||||
namespace nulib\web\curl;
|
||||
|
||||
use nulib\UserException;
|
||||
use Throwable;
|
||||
|
@ -7,16 +7,16 @@ use Throwable;
|
|||
class CurlException extends UserException {
|
||||
function __construct($ch, ?string $message=null, $code=0, ?Throwable $previous=null) {
|
||||
if ($message === null) $message = "(unknown error)";
|
||||
$user_message = $message;
|
||||
$tech_message = null;
|
||||
$userMessage = $message;
|
||||
$techMessage = null;
|
||||
if ($ch !== null) {
|
||||
$parts = [];
|
||||
$errno = curl_errno($ch);
|
||||
if ($errno != 0) $parts[] = "errno: $errno";
|
||||
$error = curl_error($ch);
|
||||
if ($error != "") $parts[] = "error: $error";
|
||||
if ($parts) $tech_message = implode(", ", $parts);
|
||||
if ($parts) $techMessage = implode(", ", $parts);
|
||||
}
|
||||
parent::__construct($user_message, $tech_message, $code, $previous);
|
||||
parent::__construct($userMessage, $techMessage, $code, $previous);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue