Compare commits
73 Commits
Author | SHA1 | Date | |
---|---|---|---|
68f5dc5209 | |||
f005692cd8 | |||
d844bd03ad | |||
c4e02d5bcf | |||
2a92a9a07e | |||
c274adb6e6 | |||
ff02ffdf4f | |||
9109e0fe39 | |||
fd1ebaf611 | |||
26817d2826 | |||
7257654753 | |||
e2bec38540 | |||
c2ec23be30 | |||
741a807420 | |||
e9f4826a94 | |||
fd9b3b29bc | |||
f37a53cfbd | |||
6e8245dbcb | |||
f4e252a6e0 | |||
19f6f9c9e1 | |||
baa770d969 | |||
c5bfa3940c | |||
39af99ffa4 | |||
1fc4e7637b | |||
ef7e8551aa | |||
9328aac9e9 | |||
62fc315b9e | |||
b9e91bd917 | |||
3608b02749 | |||
d148850c12 | |||
189c7aba68 | |||
91e6c0dcd2 | |||
bb311708d7 | |||
1feffb6c0f | |||
62987d576e | |||
5decd631e2 | |||
c98afaa98c | |||
3994855c7e | |||
6ba0a16e84 | |||
56fda96c78 | |||
d6078e8b52 | |||
69203352d8 | |||
26b483a29f | |||
6e93c7da62 | |||
66397a3bd0 | |||
eec9215431 | |||
1340c90d62 | |||
07a6d91385 | |||
41bcb07367 | |||
1ad63a92a9 | |||
cb1918e1fd | |||
8a7b60e2d3 | |||
50cf0eca33 | |||
4b84f11f99 | |||
9cf0e8045a | |||
08d5327afa | |||
4335a63d76 | |||
7227cd7bdd | |||
bb5b30480e | |||
c5c4119e69 | |||
089e4872aa | |||
fc523bfb6c | |||
e1b4ef4cc3 | |||
a705dd61c4 | |||
f8eec57055 | |||
74487a0ab9 | |||
68023b72ee | |||
1aa266b509 | |||
1685a40906 | |||
998d353758 | |||
1a5ca79ef7 | |||
2812046b4b | |||
9438aaf396 |
16
.composer.pman.yml
Normal file
16
.composer.pman.yml
Normal file
@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 mode: yaml -*- vim:sw=2:sts=2:et:ai:si:sta:fenc=utf-8
|
||||
|
||||
composer:
|
||||
profiles: [ dev, dist ]
|
||||
dev:
|
||||
link: true
|
||||
require-dev:
|
||||
nulib/php: ^7.4-dev
|
||||
nulib/spout: ^7.4-dev
|
||||
nulib/phpss: ^7.4-dev
|
||||
dist:
|
||||
link: false
|
||||
require-dev:
|
||||
nulib/php: ^0.4.0p74
|
||||
nulib/spout: ^0.4.0p74
|
||||
nulib/phpss: ^0.4.0p74
|
7
.idea/inspectionProfiles/Project_Default.xml
generated
7
.idea/inspectionProfiles/Project_Default.xml
generated
@ -2,5 +2,12 @@
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="GrazieInspection" enabled="false" level="GRAMMAR_ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||
<option name="processCode" value="true" />
|
||||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
6
.idea/nur-ture.iml
generated
6
.idea/nur-ture.iml
generated
@ -4,10 +4,8 @@
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/nur_src" isTestSource="false" packagePrefix="nur\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/nur_tests" isTestSource="true" packagePrefix="nur\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="nur\sery\wip\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src_app" isTestSource="false" packagePrefix="nur\sery\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="nur\sery\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src_glue" isTestSource="false" packagePrefix="nulib\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="nulib\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="nulib\" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
15
.idea/php.xml
generated
15
.idea/php.xml
generated
@ -10,6 +10,11 @@
|
||||
<option name="highlightLevel" value="WARNING" />
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PhpCodeSniffer">
|
||||
<phpcs_settings>
|
||||
<phpcs_by_interpreter asDefaultInterpreter="true" interpreter_id="846389f7-9fb5-4173-a868-1dc6b8fbb3fa" timeout="30000" />
|
||||
</phpcs_settings>
|
||||
</component>
|
||||
<component name="PhpIncludePathManager">
|
||||
<include_path>
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/code-unit-reverse-lookup" />
|
||||
@ -64,6 +69,11 @@
|
||||
</include_path>
|
||||
</component>
|
||||
<component name="PhpProjectSharedConfiguration" php_language_level="7.4" />
|
||||
<component name="PhpStan">
|
||||
<PhpStan_settings>
|
||||
<phpstan_by_interpreter asDefaultInterpreter="true" interpreter_id="846389f7-9fb5-4173-a868-1dc6b8fbb3fa" timeout="60000" />
|
||||
</PhpStan_settings>
|
||||
</component>
|
||||
<component name="PhpStanOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
@ -72,6 +82,11 @@
|
||||
<PhpUnitSettings custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" />
|
||||
</phpunit_settings>
|
||||
</component>
|
||||
<component name="Psalm">
|
||||
<Psalm_settings>
|
||||
<psalm_fixer_by_interpreter asDefaultInterpreter="true" interpreter_id="846389f7-9fb5-4173-a868-1dc6b8fbb3fa" timeout="60000" />
|
||||
</Psalm_settings>
|
||||
</component>
|
||||
<component name="PsalmOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
|
17
.pman.conf
17
.pman.conf
@ -9,4 +9,19 @@ TAG_PREFIX=
|
||||
TAG_SUFFIX=p74
|
||||
HOTFIX=hotf74-
|
||||
DIST=
|
||||
NOAUTO=1
|
||||
NOAUTO=
|
||||
|
||||
AFTER_CREATE_RELEASE='
|
||||
set -x
|
||||
pman --composer-select-profile dist
|
||||
composer u || exit 1
|
||||
git commit -am "<pman>deps de dist"
|
||||
true
|
||||
'
|
||||
AFTER_MERGE_RELEASE='
|
||||
set -x
|
||||
pman --composer-select-profile dev
|
||||
composer u || exit 1
|
||||
git commit -am "<pman>deps de dev"
|
||||
true
|
||||
'
|
||||
|
21
CHANGES.md
21
CHANGES.md
@ -1,3 +1,24 @@
|
||||
## Release 0.4.1p82 du 17/03/2025-17:23
|
||||
|
||||
## Release 0.4.1p74 du 17/03/2025-17:19
|
||||
|
||||
* `56fda96` changer le type de variables gérées par EnvConfig
|
||||
|
||||
## Release 0.4.0p82 du 14/03/2025-15:46
|
||||
|
||||
## Release 0.4.0p74 du 14/03/2025-15:44
|
||||
|
||||
* `4b84f11` début assocSchema
|
||||
* `7227cd7` maj TODO
|
||||
* `089e487` renommer dest en value; tenir compte de la valeur par défaut
|
||||
* `fc523bf` instancier type le plus vite possible
|
||||
* `f8eec57` renommer Value en Wrapper
|
||||
* `1aa266b` tstring et trawstring
|
||||
* `1a5ca79` support des colonnes
|
||||
* `9438aaf` ajout config .pman.yml
|
||||
|
||||
## Release 0.3.0p82 du 01/03/2025-13:49
|
||||
|
||||
## Release 0.3.0p74 du 01/03/2025-13:44
|
||||
|
||||
release initiale
|
||||
|
@ -1 +1 @@
|
||||
0.3.0
|
||||
0.4.1
|
||||
|
@ -64,9 +64,7 @@
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"nulib\\": "src_glue",
|
||||
"nur\\sery\\wip\\": "src",
|
||||
"nur\\sery\\": "src_app",
|
||||
"nulib\\": "src",
|
||||
"nur\\": "nur_src"
|
||||
},
|
||||
"files": [
|
||||
@ -75,7 +73,7 @@
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"nur\\sery\\": "tests",
|
||||
"nulib\\": "tests",
|
||||
"nur\\": "nur_tests"
|
||||
}
|
||||
},
|
||||
|
6
composer.lock
generated
6
composer.lock
generated
@ -589,7 +589,7 @@
|
||||
"dist": {
|
||||
"type": "path",
|
||||
"url": "../nulib",
|
||||
"reference": "939f7726ab139071e8a3ef5c83fc147fba859d9d"
|
||||
"reference": "1536e091fb0020858204f59462a7a80b5f9775d9"
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
@ -637,7 +637,7 @@
|
||||
"dist": {
|
||||
"type": "path",
|
||||
"url": "../nulib-phpss",
|
||||
"reference": "a78623f5ae6891144b9581709847328b93342e1d"
|
||||
"reference": "9e4f41e38deef10993d859202988567db9d4fada"
|
||||
},
|
||||
"require": {
|
||||
"nulib/php": "^7.4-dev",
|
||||
@ -681,7 +681,7 @@
|
||||
"dist": {
|
||||
"type": "path",
|
||||
"url": "../nulib-spout",
|
||||
"reference": "f9c420058015d02e913f0d65c41242ec7b8a0cee"
|
||||
"reference": "65c74a1db6dda718aa20970ede3dfa4b4d32c791"
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
|
@ -2,6 +2,6 @@
|
||||
<?php
|
||||
require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
use nur\sery\tools\SteamTrainApp;
|
||||
use nulib\tools\SteamTrainApp;
|
||||
|
||||
SteamTrainApp::run();
|
||||
|
@ -2,6 +2,7 @@
|
||||
namespace nur\cli;
|
||||
|
||||
use Exception;
|
||||
use nulib\app;
|
||||
use nulib\app\RunFile;
|
||||
use nulib\ExitError;
|
||||
use nulib\ext\yaml;
|
||||
@ -16,7 +17,6 @@ use nur\config\ArrayConfig;
|
||||
use nur\msg;
|
||||
use nur\os;
|
||||
use nur\path;
|
||||
use nur\sery\app;
|
||||
|
||||
/**
|
||||
* Class Application: application de base
|
||||
|
@ -15,10 +15,9 @@ use Throwable;
|
||||
* profil par défaut est dépendant de l'implémentation de IConfigManager
|
||||
* utilisée. Dans l'implémentation actuelle, 'ALL' est le profil par défaut.
|
||||
* - PKEY est le chemin de clé dans lequel les caractères '.' sont remplacés
|
||||
* par '__' et '-' par '_' (celà signifie qu'il n'est pas possible de définir
|
||||
* un chemin de clé qui contient le caractère '_')
|
||||
* par '__'
|
||||
*
|
||||
* par exemple, la valeur dbs.my-auth.type du profil par défaut est pris dans
|
||||
* par exemple, la valeur dbs.my_auth.type du profil par défaut est pris dans
|
||||
* la variable 'CONFIG_ALL_dbs__my_auth__type'. pour le profil prod c'est la
|
||||
* variable 'CONFIG_prod_dbs__my_auth__type'
|
||||
*
|
||||
@ -51,7 +50,9 @@ class EnvConfig extends DynConfig {
|
||||
$profile = substr($name, 0, $i);
|
||||
$name = substr($name, $i + 1);
|
||||
$pkey = str_replace("__", ".", $name);
|
||||
$pkey = str_replace("_", "-", $pkey);
|
||||
#XXX désactiver parce que les configurations sont plus généralement avec
|
||||
# le caractères '_', par le caractères '-'
|
||||
//$pkey = str_replace("_", "-", $pkey);
|
||||
return [$pkey, $profile];
|
||||
}
|
||||
|
||||
|
@ -105,7 +105,7 @@ class ref_type {
|
||||
/** comme {@link c} mais nullable */
|
||||
const NCONTENT = "?".self::CONTENT;
|
||||
|
||||
/** comme {@link \nur\sery\FILE} mais nullable */
|
||||
/** comme {@link FILE} mais nullable */
|
||||
const NFILE = "?".self::FILE;
|
||||
|
||||
/** comme {@link DATETIME} mais nullable */
|
||||
|
@ -32,6 +32,7 @@ class nb {
|
||||
}
|
||||
|
||||
static final function menu($text, ?array $links=null, ?array $options=null): array {
|
||||
$links = array_filter($links, function($link) { return $link !== null; });
|
||||
$item = ["item" => "menu", "links" => $links, "value" => $text];
|
||||
if ($options !== null) $item = array_merge($item, $options);
|
||||
return $item;
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace nur\v\vp;
|
||||
|
||||
use nulib\app;
|
||||
use nur\authz;
|
||||
use nur\b\authnz\IAuthzUser;
|
||||
use nur\config;
|
||||
@ -46,6 +47,21 @@ class NavigablePage extends AInitAuthzPage implements INavigablePage {
|
||||
|
||||
const MENU_SULOGIN = true;
|
||||
|
||||
protected function getAppVersionNbtext(): ?array {
|
||||
$app = app::get();
|
||||
$projdir = $app->getProjdir();
|
||||
$versionfile = "$projdir/VERSION.txt";
|
||||
if (file_exists($versionfile)) {
|
||||
$name = $app->getName();
|
||||
$version = file_get_contents($versionfile);
|
||||
return nb::text([
|
||||
"style" => "margin: 0 15px",
|
||||
"$name v$version"
|
||||
]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getAuthzNbtext(IAuthzUser $user): array {
|
||||
$username = $user->getUsername();
|
||||
$role = $user->getRole();
|
||||
@ -95,6 +111,7 @@ class NavigablePage extends AInitAuthzPage implements INavigablePage {
|
||||
$user = authz::get();
|
||||
navbar::nav(["align" => "right"], [
|
||||
nb::menu(icon::user($user->getShortName()), [
|
||||
$this->getAppVersionNbtext(),
|
||||
$this->getAuthzNbtext($user),
|
||||
$this->getLogoutNblink(),
|
||||
]),
|
||||
|
@ -2,7 +2,7 @@
|
||||
require(__DIR__.'/../../vendor/autoload.php');
|
||||
|
||||
use nur\cli\Application;
|
||||
use nur\sery\output\msg;
|
||||
use nulib\output\msg;
|
||||
|
||||
class TestArgs4 extends Application {
|
||||
protected $query;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace nur\sery;
|
||||
namespace nulib;
|
||||
|
||||
use nulib\A;
|
||||
use nulib\app\LockFile;
|
||||
@ -12,7 +12,7 @@ use nulib\php\func;
|
||||
use nulib\str;
|
||||
use nulib\ValueException;
|
||||
use nur\cli\Application as nur_Application;
|
||||
use nur\sery\app\cli\Application;
|
||||
use nulib\app\cli\Application;
|
||||
|
||||
class app {
|
||||
private static function isa_Application($app): bool {
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace nur\sery\app\cli;
|
||||
namespace nulib\app\cli;
|
||||
|
||||
use Exception;
|
||||
use nulib\app\RunFile;
|
||||
@ -13,7 +13,7 @@ use nulib\ValueException;
|
||||
use nur\cli\ArgsException;
|
||||
use nur\cli\ArgsParser;
|
||||
use nur\config;
|
||||
use nur\sery\app;
|
||||
use nulib\app;
|
||||
|
||||
/**
|
||||
* Class Application: application de base
|
@ -1,13 +1,25 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\php\access;
|
||||
namespace nulib\php\access;
|
||||
|
||||
use nur\sery\cl;
|
||||
use nulib\cl;
|
||||
|
||||
/**
|
||||
* Class AbstractAccess: implémentation par défaut pour des instances standard
|
||||
* de {@link IAccess}
|
||||
*/
|
||||
abstract class AbstractAccess implements IAccess {
|
||||
const ALLOW_EMPTY = true;
|
||||
|
||||
function __construct(?array $params=null) {
|
||||
$this->allowEmpty = $params["allow_empty"] ?? static::ALLOW_EMPTY;
|
||||
}
|
||||
|
||||
protected bool $allowEmpty;
|
||||
|
||||
function isAllowEmpty(): bool {
|
||||
return $this->allowEmpty;
|
||||
}
|
||||
|
||||
function inc(): int {
|
||||
$value = (int)$this->get();
|
||||
$this->set(++$value);
|
||||
@ -33,4 +45,13 @@ abstract class AbstractAccess implements IAccess {
|
||||
cl::set($array, $key, $value);
|
||||
$this->set($array);
|
||||
}
|
||||
|
||||
function ensureAssoc(array $keys, ?array $params=null): void {
|
||||
}
|
||||
|
||||
function ensureKeys(array $defaults, ?array $params=null): void {
|
||||
}
|
||||
|
||||
function ensureOrder(array $keys, ?array $params=null): void {
|
||||
}
|
||||
}
|
||||
|
8
src/php/access/ArrayAccess.php
Normal file
8
src/php/access/ArrayAccess.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
namespace nulib\php\access;
|
||||
|
||||
class ArrayAccess extends KeyAccess {
|
||||
const ALLOW_NULL = true;
|
||||
const ALLOW_FALSE = false;
|
||||
const PROTECT_DEST = true;
|
||||
}
|
182
src/php/access/ChainAccess.php
Normal file
182
src/php/access/ChainAccess.php
Normal file
@ -0,0 +1,182 @@
|
||||
<?php
|
||||
namespace nulib\php\access;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\StateException;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
|
||||
class ChainAccess extends AbstractAccess {
|
||||
const ACCESS_AUTO = 0, ACCESS_KEY = 1, ACCESS_PROPERTY = 2;
|
||||
|
||||
private static function unexpected_access_type(): StateException {
|
||||
return StateException::unexpected_state("access_type");
|
||||
}
|
||||
|
||||
function __construct(IAccess $access, $key, ?array $params=null) {
|
||||
parent::__construct();
|
||||
$this->access = $access;
|
||||
$this->key = $key;
|
||||
$this->accessType = $params["access_type"] ?? self::ACCESS_AUTO;
|
||||
}
|
||||
|
||||
protected IAccess $access;
|
||||
|
||||
/** @var null|int|string|array */
|
||||
protected $key;
|
||||
|
||||
protected int $accessType;
|
||||
|
||||
protected function _accessType($access, $key): int {
|
||||
$accessType = $this->accessType;
|
||||
if ($accessType === self::ACCESS_AUTO) {
|
||||
if (is_object($access) && is_string($key)) {
|
||||
$accessType = self::ACCESS_PROPERTY;
|
||||
} else {
|
||||
$accessType = self::ACCESS_KEY;
|
||||
}
|
||||
}
|
||||
return $accessType;
|
||||
}
|
||||
|
||||
protected function _has(): bool {
|
||||
$src = $this->access->get();
|
||||
$key = $this->key;
|
||||
$accessType = $this->_accessType($src, $key);
|
||||
if ($accessType === self::ACCESS_KEY) {
|
||||
return cl::phas($src, $key);
|
||||
} elseif ($accessType === self::ACCESS_PROPERTY) {
|
||||
$class = new ReflectionClass($src);
|
||||
return $class->hasProperty($key) || property_exists($src, $key);
|
||||
} else {
|
||||
throw self::unexpected_access_type();
|
||||
}
|
||||
}
|
||||
|
||||
protected function _get($default=null) {
|
||||
$src = $this->access->get();
|
||||
$key = $this->key;
|
||||
$accessType = $this->_accessType($src, $key);
|
||||
if ($accessType === self::ACCESS_KEY) {
|
||||
return cl::pget($src, $key);
|
||||
} elseif ($accessType === self::ACCESS_PROPERTY) {
|
||||
$class = new ReflectionClass($src);
|
||||
try {
|
||||
$property = $class->getProperty($key);
|
||||
$property->setAccessible(true);
|
||||
} catch (ReflectionException $e) {
|
||||
$property = null;
|
||||
}
|
||||
if ($property !== null) {
|
||||
return $property->getValue($src);
|
||||
} elseif (property_exists($src, $key)) {
|
||||
return $src->$key;
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
} else {
|
||||
throw self::unexpected_access_type();
|
||||
}
|
||||
}
|
||||
|
||||
protected function _pset(object $dest, $name, $value): void {
|
||||
$class = new ReflectionClass($dest);
|
||||
try {
|
||||
$property = $class->getProperty($name);
|
||||
$property->setAccessible(true);
|
||||
} catch (ReflectionException $e) {
|
||||
$property = null;
|
||||
}
|
||||
if ($property !== null) {
|
||||
$property->setValue($dest, $value);
|
||||
} else {
|
||||
$dest->$name = $value;
|
||||
}
|
||||
}
|
||||
|
||||
function isAllowEmpty(): bool {
|
||||
return $this->access->isAllowEmpty();
|
||||
}
|
||||
|
||||
function exists(): bool {
|
||||
if (!$this->access->exists()) return false;
|
||||
if ($this->key === null) return true;
|
||||
return $this->_has();
|
||||
}
|
||||
|
||||
function available(): bool {
|
||||
if (!$this->access->available()) return false;
|
||||
if ($this->key === null) return true;
|
||||
if (!$this->_has()) return false;
|
||||
return $this->isAllowEmpty() || $this->_get() !== "";
|
||||
}
|
||||
|
||||
function get($default=null) {
|
||||
if ($this->key === null) {
|
||||
return $this->access->get($default);
|
||||
}
|
||||
return $this->_get($default);
|
||||
}
|
||||
|
||||
function set($value): void {
|
||||
if ($this->key === null) {
|
||||
$this->access->set($value);
|
||||
return;
|
||||
}
|
||||
$dest = $this->access->get();
|
||||
$key = $this->key;
|
||||
$accessType = $this->_accessType($dest, $key);
|
||||
if ($accessType === self::ACCESS_KEY) {
|
||||
cl::pset($dest, $key, $value);
|
||||
$this->access->set($dest);
|
||||
} elseif ($accessType === self::ACCESS_PROPERTY) {
|
||||
$this->_pset($dest, $key, $value);
|
||||
} else {
|
||||
throw self::unexpected_access_type();
|
||||
}
|
||||
}
|
||||
|
||||
function del(): void {
|
||||
if ($this->key === null) {
|
||||
$this->access->del();
|
||||
return;
|
||||
}
|
||||
$dest = $this->access->get();
|
||||
$key = $this->key;
|
||||
$accessType = $this->_accessType($dest, $key);
|
||||
if ($accessType === self::ACCESS_KEY) {
|
||||
cl::pdel($dest, $key);
|
||||
$this->access->set($dest);
|
||||
} elseif ($accessType === self::ACCESS_PROPERTY) {
|
||||
$this->_pset($dest, $key, null);
|
||||
} else {
|
||||
throw self::unexpected_access_type();
|
||||
}
|
||||
}
|
||||
|
||||
function addKey($key, ?array $params=null): IAccess {
|
||||
if ($key === null) return $this;
|
||||
$accessType = $params["access_type"] ?? $this->accessType;
|
||||
if ($accessType === self::ACCESS_KEY && $accessType === $this->accessType) {
|
||||
if ($this->key !== null) $key = cl::merge($this->key, $key);
|
||||
return new ChainAccess($this->access, $key);
|
||||
} else {
|
||||
return new ChainAccess($this, $key);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureAssoc(array $keys, ?array $params=null): void {
|
||||
#XXX fonction de $accessType?
|
||||
#$this->access->ensureAssoc($keys, $params);
|
||||
}
|
||||
|
||||
function ensureKeys(array $defaults, ?array $params=null): void {
|
||||
#XXX fonction de $accessType?
|
||||
#$this->access->ensureKeys($defaults, $params);
|
||||
}
|
||||
|
||||
function ensureOrder(array $keys, ?array $params=null): void {
|
||||
#XXX fonction de $accessType?
|
||||
#$this->access->ensureOrder($keys, $params);
|
||||
}
|
||||
}
|
@ -1,73 +1,105 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\php\access;
|
||||
namespace nulib\php\access;
|
||||
|
||||
use nur\sery\cl;
|
||||
use nulib\cl;
|
||||
|
||||
/**
|
||||
* Class FormAccess: accès à une valeur de $_POST puis $_GET, dans cet ordre
|
||||
*/
|
||||
class FormAccess extends AbstractAccess {
|
||||
const ALLOW_EMPTY = false;
|
||||
|
||||
function __construct($key, ?array $params=null) {
|
||||
parent::__construct($params);
|
||||
$this->key = $key;
|
||||
$this->allowEmpty = $params["allow_empty"] ?? false;
|
||||
}
|
||||
|
||||
/** @var int|string */
|
||||
/** @var null|int|string|array */
|
||||
protected $key;
|
||||
|
||||
protected bool $allowEmpty;
|
||||
|
||||
function exists(): bool {
|
||||
protected function _exists(array $first, ?array $second=null): bool {
|
||||
$key = $this->key;
|
||||
if ($key === null) return false;
|
||||
return array_key_exists($key, $_POST) || array_key_exists($key, $_GET);
|
||||
if ($key === null) return true;
|
||||
if (cl::phas($first, $key)) return true;
|
||||
return $second !== null && cl::phas($second, $key);
|
||||
}
|
||||
|
||||
public function available(): bool {
|
||||
function exists(): bool {
|
||||
return $this->_exists($_POST, $_GET);
|
||||
}
|
||||
|
||||
protected function _available(array $first, ?array $second=null): bool {
|
||||
$key = $this->key;
|
||||
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] !== "";
|
||||
if ($key === null) return true;
|
||||
if (cl::phas($first, $key)) {
|
||||
return $this->allowEmpty || cl::pget($first, $key) !== "";
|
||||
} elseif ($second !== null && cl::phas($second, $key)) {
|
||||
return $this->allowEmpty || cl::pget($second, $key) !== "";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function get($default=null) {
|
||||
public function available(): bool {
|
||||
return $this->_available($_POST, $_GET);
|
||||
}
|
||||
|
||||
protected function _get($default, array $first, ?array $second=null) {
|
||||
$key = $this->key;
|
||||
if ($key === null) return $default;
|
||||
if (array_key_exists($key, $_POST)) {
|
||||
$value = $_POST[$key];
|
||||
if ($value === "" && !$this->allowEmpty) return $default;
|
||||
return $value;
|
||||
} elseif (array_key_exists($key, $_GET)) {
|
||||
$value = $_GET[$key];
|
||||
if ($value === "" && !$this->allowEmpty) return $default;
|
||||
return $value;
|
||||
if ($key === null) return cl::merge($first, $second);
|
||||
if (cl::phas($first, $key)) {
|
||||
$value = cl::pget($first, $key);
|
||||
if ($value !== "" || $this->allowEmpty) return $value;
|
||||
} elseif ($second !== null && cl::phas($second, $key)) {
|
||||
$value = cl::pget($second, $key);
|
||||
if ($value !== "" || $this->allowEmpty) return $value;
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
function get($default=null) {
|
||||
return $this->_get($default, $_POST, $_GET);
|
||||
}
|
||||
|
||||
function _set($value, array &$first, ?array &$second=null): void {
|
||||
$key = $this->key;
|
||||
if ($key === null) {
|
||||
# interdire la modification de la destination
|
||||
return;
|
||||
}
|
||||
if ($second !== null && !cl::phas($first, $key) && cl::phas($second, $key)) {
|
||||
cl::pset($second, $key, $value);
|
||||
} else {
|
||||
return $default;
|
||||
cl::pset($first, $key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
function set($value): void {
|
||||
$this->_set($value, $_POST, $_GET);
|
||||
}
|
||||
|
||||
function _del(array &$first, ?array &$second=null): void {
|
||||
$key = $this->key;
|
||||
if ($key === null) return;
|
||||
if (!array_key_exists($key, $_POST) && array_key_exists($key, $_GET)) {
|
||||
cl::set($_GET, $key, $value);
|
||||
if ($key === null) {
|
||||
# interdire la modification de la destination
|
||||
return;
|
||||
}
|
||||
if ($second !== null && !cl::phas($first, $key) && cl::phas($second, $key)) {
|
||||
cl::pdel($second, $key);
|
||||
} else {
|
||||
cl::set($_POST, $key, $value);
|
||||
cl::pdel($first, $key);
|
||||
}
|
||||
}
|
||||
|
||||
function del(): void {
|
||||
$key = $this->key;
|
||||
if ($key === null) return;
|
||||
if (!array_key_exists($key, $_POST) && array_key_exists($key, $_GET)) {
|
||||
cl::del($_GET, $key);
|
||||
} else {
|
||||
cl::del($_POST, $key);
|
||||
}
|
||||
$this->_del($_POST, $_GET);
|
||||
}
|
||||
|
||||
function addKey($key): self {
|
||||
if ($key === null) return $this;
|
||||
if ($this->key !== null) $key = cl::merge($this->key, $key);
|
||||
return new static($key, [
|
||||
"allow_empty" => $this->allowEmpty
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -1,49 +1,29 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\php\access;
|
||||
namespace nulib\php\access;
|
||||
|
||||
use nur\sery\cl;
|
||||
use nulib\cl;
|
||||
|
||||
/**
|
||||
* Class GetAccess: accès à une valeur de $_GET
|
||||
*/
|
||||
class GetAccess extends FormAccess {
|
||||
function exists(): bool {
|
||||
$key = $this->key;
|
||||
if ($key === null) return false;
|
||||
return array_key_exists($key, $_GET);
|
||||
return $this->_exists($_GET);
|
||||
}
|
||||
|
||||
public function available(): bool {
|
||||
$key = $this->key;
|
||||
if ($key === null) return false;
|
||||
if (array_key_exists($key, $_GET)) {
|
||||
return $this->allowEmpty || $_GET[$key] !== "";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return $this->_available($_GET);
|
||||
}
|
||||
|
||||
function get($default=null) {
|
||||
$key = $this->key;
|
||||
if ($key === null) return $default;
|
||||
if (array_key_exists($key, $_GET)) {
|
||||
$value = $_GET[$key];
|
||||
if ($value === "" && !$this->allowEmpty) return $default;
|
||||
return $value;
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
return $this->_get($default, $_GET);
|
||||
}
|
||||
|
||||
function set($value): void {
|
||||
$key = $this->key;
|
||||
if ($key === null) return;
|
||||
cl::set($_GET, $key, $value);
|
||||
$this->_set($value, $_GET);
|
||||
}
|
||||
|
||||
function del(): void {
|
||||
$key = $this->key;
|
||||
if ($key === null) return;
|
||||
cl::del($_GET, $key);
|
||||
$this->_del($_GET);
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,16 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\php\access;
|
||||
namespace nulib\php\access;
|
||||
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* Interface IAccess: abstraction d'un accès complet à une valeur
|
||||
*/
|
||||
interface IAccess extends IGetter, ISetter, IDeleter {
|
||||
/** incrémenter la valeur */
|
||||
/** incrémenter la valeur et la retourner */
|
||||
function inc(): int;
|
||||
|
||||
/** décrémenter la valeur */
|
||||
/** décrémenter la valeur et la retourner */
|
||||
function dec(bool $allowNegative=false): int;
|
||||
|
||||
/**
|
||||
@ -25,4 +27,26 @@ interface IAccess extends IGetter, ISetter, IDeleter {
|
||||
* tableau si $key===null
|
||||
*/
|
||||
function append($value, $key=null): void;
|
||||
|
||||
/** retourner une instance permettant d'accéder à $value[$key] */
|
||||
function addKey($key): IAccess;
|
||||
|
||||
/**
|
||||
* s'assurer que la destination est un tableau associatif en remplaçant les
|
||||
* clés numériques par les clés correspondantes du tableau $keys
|
||||
*/
|
||||
function ensureAssoc(array $keys, ?array $params=null): void;
|
||||
|
||||
/**
|
||||
* s'assurer que toutes les clés mentionnées dans le tableau $defaults
|
||||
* existent. si elles n'existent pas, leur donner la valeur du tableau
|
||||
* $defaults
|
||||
*/
|
||||
function ensureKeys(array $defaults, ?array $params=null): void;
|
||||
|
||||
/**
|
||||
* s'assure que les clés de la destination sont dans l'ordre mentionné dans le
|
||||
* tableau $keys. toutes les clés supplémentaires sont placées à la fin
|
||||
*/
|
||||
function ensureOrder(array $keys, ?array $params=null): void;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\php\access;
|
||||
namespace nulib\php\access;
|
||||
|
||||
/**
|
||||
* Class IDeleter: une abstraction d'un objet qui permet de supprimer une valeur
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\php\access;
|
||||
namespace nulib\php\access;
|
||||
|
||||
/**
|
||||
* Class IGetter: une abstraction d'un objet qui permet d'obtenir une valeur
|
||||
@ -11,6 +11,12 @@ interface IGetter {
|
||||
*/
|
||||
function exists(): bool;
|
||||
|
||||
/**
|
||||
* @return bool true si cet objet autorise les chaines vides. si c'est le cas,
|
||||
* {@link exists()} et {@link available()} sont fonctionnellement identiques
|
||||
*/
|
||||
function isAllowEmpty(): bool;
|
||||
|
||||
/** @return bool true si la valeur existe et est utilisable, false sinon */
|
||||
function available(): bool;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\php\access;
|
||||
namespace nulib\php\access;
|
||||
|
||||
/**
|
||||
* Class ISetter: une abstraction d'un objet qui permet de modifier une valeur
|
||||
|
@ -1,69 +1,188 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\php\access;
|
||||
namespace nulib\php\access;
|
||||
|
||||
use ArrayAccess;
|
||||
use nur\sery\cl;
|
||||
use nulib\cl;
|
||||
use nulib\ref\schema\ref_schema;
|
||||
|
||||
/**
|
||||
* Class KeyAccess: accès à une valeur d'une clé dans un tableau
|
||||
* Class KeyAccess: accès
|
||||
* - soit à une valeur d'un chemin de clé dans un tableau (si $key !== null)
|
||||
* - soit à une valeur scalaire (si $key === null)
|
||||
*/
|
||||
class KeyAccess extends AbstractAccess {
|
||||
function __construct(&$dest, $key, ?array $params=null) {
|
||||
const ALLOW_NULL = null;
|
||||
const ALLOW_FALSE = null;
|
||||
const PROTECT_DEST = false;
|
||||
|
||||
function __construct(&$dest, $key=null, ?array $params=null) {
|
||||
parent::__construct($params);
|
||||
$this->protectDest = $params["protect_dest"] ?? static::PROTECT_DEST;
|
||||
$this->dest =& $dest;
|
||||
$this->key = $key;
|
||||
$this->allowNull = $params["allow_null"] ?? true;
|
||||
$this->allowFalse = $params["allow_false"] ?? false;
|
||||
$this->allowEmpty = $params["allow_empty"] ?? true;
|
||||
$this->allowNull = $params["allow_null"] ?? static::ALLOW_NULL;
|
||||
$this->allowFalse = $params["allow_false"] ?? static::ALLOW_FALSE;
|
||||
}
|
||||
|
||||
/** @var array|ArrayAccess */
|
||||
protected bool $protectDest;
|
||||
|
||||
/** @var mixed|array|ArrayAccess */
|
||||
protected $dest;
|
||||
|
||||
function reset(&$dest): self {
|
||||
/** @var null|int|string|array */
|
||||
protected $key;
|
||||
|
||||
function reset(&$dest, $key=null): self {
|
||||
$this->dest =& $dest;
|
||||
$this->key = $key;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @var int|string */
|
||||
protected $key;
|
||||
function resetKey($key=null): self {
|
||||
$this->key = $key;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected bool $allowNull;
|
||||
protected ?bool $allowNull;
|
||||
|
||||
protected bool $allowFalse;
|
||||
protected function isAllowNull(): bool {
|
||||
$allowNull = $this->allowNull;
|
||||
if ($allowNull !== null) return $allowNull;
|
||||
return $this->key !== null;
|
||||
}
|
||||
|
||||
protected bool $allowEmpty;
|
||||
protected ?bool $allowFalse;
|
||||
|
||||
protected function isAllowFalse(): bool {
|
||||
$allowFalse = $this->allowFalse;
|
||||
if ($allowFalse !== null) return $allowFalse;
|
||||
return $this->key === null;
|
||||
}
|
||||
|
||||
function exists(): bool {
|
||||
$key = $this->key;
|
||||
if ($key === null) return false;
|
||||
return cl::has($this->dest, $key);
|
||||
if ($key === null) {
|
||||
return $this->isAllowNull() || $this->dest !== null;
|
||||
} else {
|
||||
return cl::phas($this->dest, $key);
|
||||
}
|
||||
}
|
||||
|
||||
function available(): bool {
|
||||
if (!$this->exists()) return false;
|
||||
$value = cl::get($this->dest, $this->key);
|
||||
if ($value === null) return $this->allowNull;
|
||||
if ($value === false) return $this->allowFalse;
|
||||
$key = $this->key;
|
||||
if ($key === null) $value = $this->dest;
|
||||
else $value = cl::pget($this->dest, $key);
|
||||
if ($value === "") return $this->allowEmpty;
|
||||
if ($value === null) return $this->isAllowNull();
|
||||
if ($value === false) return $this->isAllowFalse();
|
||||
return true;
|
||||
}
|
||||
|
||||
function get($default=null) {
|
||||
if ($this->key === null) return $default;
|
||||
$value = cl::get($this->dest, $this->key, $default);
|
||||
if ($value === null && !$this->allowNull) return $default;
|
||||
if ($value === false && !$this->allowFalse) return $default;
|
||||
$key = $this->key;
|
||||
if ($key === null) $value = $this->dest;
|
||||
else $value = cl::pget($this->dest, $key, $default);
|
||||
if ($value === "" && !$this->allowEmpty) return $default;
|
||||
if ($value === null && !$this->isAllowNull()) return $default;
|
||||
if ($value === false && !$this->isAllowFalse()) return $default;
|
||||
return $value;
|
||||
}
|
||||
|
||||
function set($value): void {
|
||||
if ($this->key === null) return;
|
||||
cl::set($this->dest, $this->key, $value);
|
||||
$key = $this->key;
|
||||
if ($key === null) {
|
||||
if (!$this->protectDest) $this->dest = $value;
|
||||
} else {
|
||||
cl::pset($this->dest, $key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
function del(): void {
|
||||
if ($this->key === null) return;
|
||||
cl::del($this->dest, $this->key);
|
||||
$key = $this->key;
|
||||
if ($key === null) {
|
||||
if (!$this->protectDest) $this->dest = null;
|
||||
} else {
|
||||
cl::pdel($this->dest, $key);
|
||||
}
|
||||
}
|
||||
|
||||
function addKey($key): self {
|
||||
if ($key === null) return $this;
|
||||
if ($this->key !== null) $key = cl::merge($this->key, $key);
|
||||
return new KeyAccess($this->dest, $key, [
|
||||
"allow_empty" => $this->allowEmpty,
|
||||
"allow_null" => $this->allowNull,
|
||||
"allow_false" => $this->allowFalse,
|
||||
"protect_dest" => $this->protectDest,
|
||||
]);
|
||||
}
|
||||
|
||||
function ensureAssoc(array $keys, ?array $params=null): void {
|
||||
$dest =& $this->dest;
|
||||
$prefix = $params["key_prefix"] ?? null;
|
||||
$suffix = $params["key_suffix"] ?? null;
|
||||
$index = 0;
|
||||
foreach ($keys as $key) {
|
||||
if ($prefix !== null || $suffix !== null) {
|
||||
$destKey = "$prefix$key$suffix";
|
||||
} else {
|
||||
# préserver les clés numériques
|
||||
$destKey = $key;
|
||||
}
|
||||
if ($dest !== null && array_key_exists($destKey, $dest)) continue;
|
||||
while (in_array($index, $keys, true)) {
|
||||
$index++;
|
||||
}
|
||||
if ($dest !== null && array_key_exists($index, $dest)) {
|
||||
$dest[$destKey] = $dest[$index];
|
||||
unset($dest[$index]);
|
||||
$index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ensureKeys(array $defaults, ?array $params=null): void {
|
||||
$dest =& $this->dest;
|
||||
$keys = array_keys($defaults);
|
||||
$prefix = $params["key_prefix"] ?? null;
|
||||
$suffix = $params["key_suffix"] ?? null;
|
||||
foreach ($keys as $key) {
|
||||
$destKey = "$prefix$key$suffix";
|
||||
if ($dest === null || !array_key_exists($destKey, $dest)) {
|
||||
$dest[$destKey] = $defaults[$key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ensureOrder(array $keys, ?array $params=null): void {
|
||||
$dest =& $this->dest;
|
||||
if ($dest === null) return;
|
||||
|
||||
$prefix = $params["key_prefix"] ?? null;
|
||||
$suffix = $params["key_suffix"] ?? null;
|
||||
if ($prefix !== null || $suffix !== null) {
|
||||
foreach ($keys as &$key) {
|
||||
$key = "$prefix$key$suffix";
|
||||
}; unset($key);
|
||||
}
|
||||
|
||||
$destKeys = array_keys($dest);
|
||||
$keyCount = count($keys);
|
||||
if (array_slice($destKeys, 0, $keyCount) === $keys) {
|
||||
# si le tableau a déjà les bonnes clés dans le bon ordre, rien à faire
|
||||
return;
|
||||
}
|
||||
|
||||
$ordered = [];
|
||||
foreach ($keys as $key) {
|
||||
if (array_key_exists($key, $dest)) {
|
||||
$ordered[$key] = $dest[$key];
|
||||
unset($dest[$key]);
|
||||
}
|
||||
}
|
||||
$preserveKeys = $params["preserve_keys"] ?? false;
|
||||
if ($preserveKeys) $dest = cl::merge2($ordered, $dest);
|
||||
else $dest = array_merge($ordered, $dest);
|
||||
}
|
||||
}
|
||||
|
@ -1,49 +1,29 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\php\access;
|
||||
namespace nulib\php\access;
|
||||
|
||||
use nur\sery\cl;
|
||||
use nulib\cl;
|
||||
|
||||
/**
|
||||
* Class PostAccess: accès à une valeur de $_POST
|
||||
*/
|
||||
class PostAccess extends FormAccess {
|
||||
function exists(): bool {
|
||||
$key = $this->key;
|
||||
if ($key === null) return false;
|
||||
return array_key_exists($key, $_POST);
|
||||
return $this->_exists($_POST);
|
||||
}
|
||||
|
||||
public function available(): bool {
|
||||
$key = $this->key;
|
||||
if ($key === null) return false;
|
||||
if (array_key_exists($key, $_POST)) {
|
||||
return $this->allowEmpty || $_POST[$key] !== "";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return $this->_available($_POST);
|
||||
}
|
||||
|
||||
function get($default=null) {
|
||||
$key = $this->key;
|
||||
if ($key === null) return $default;
|
||||
if (array_key_exists($key, $_POST)) {
|
||||
$value = $_POST[$key];
|
||||
if ($value === "" && !$this->allowEmpty) return $default;
|
||||
return $value;
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
return $this->_get($default, $_POST);
|
||||
}
|
||||
|
||||
function set($value): void {
|
||||
$key = $this->key;
|
||||
if ($key === null) return;
|
||||
cl::set($_POST, $key, $value);
|
||||
$this->_set($value, $_POST);
|
||||
}
|
||||
|
||||
function del(): void {
|
||||
$key = $this->key;
|
||||
if ($key === null) return;
|
||||
cl::del($_POST, $key);
|
||||
$this->_del($_POST);
|
||||
}
|
||||
}
|
||||
|
173
src/php/access/PropertyAccess.php
Normal file
173
src/php/access/PropertyAccess.php
Normal file
@ -0,0 +1,173 @@
|
||||
<?php
|
||||
namespace nulib\php\access;
|
||||
|
||||
use nulib\StateException;
|
||||
use nulib\str;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use ReflectionProperty;
|
||||
|
||||
class PropertyAccess extends AbstractAccess {
|
||||
const PROTECT_DEST = true;
|
||||
const MAP_NAMES = true;
|
||||
const ALLOW_NULL = true;
|
||||
const ALLOW_FALSE = false;
|
||||
|
||||
function __construct(?object $dest, ?string $name=null, ?array $params=null) {
|
||||
parent::__construct($params);
|
||||
$this->protectDest = $params["protect_dest"] ?? static::PROTECT_DEST;
|
||||
$this->mapNames = $params["map_names"] ?? static::MAP_NAMES;
|
||||
$this->_setName($name);
|
||||
$this->_setDest($dest);
|
||||
$this->allowNull = $params["allow_null"] ?? static::ALLOW_NULL;
|
||||
$this->allowFalse = $params["allow_false"] ?? static::ALLOW_FALSE;
|
||||
}
|
||||
|
||||
protected bool $protectDest;
|
||||
|
||||
protected ?object $dest;
|
||||
protected bool $mapNames;
|
||||
|
||||
protected ?string $name;
|
||||
|
||||
protected ?ReflectionProperty $property;
|
||||
|
||||
private function _getName(string $key): string {
|
||||
return $this->mapNames? str::us2camel($key): $key;
|
||||
}
|
||||
private function _setName(?string $name): void {
|
||||
if ($name !== null) $name = $this->_getName($name);
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
private function _getProperty(?string $name, ?ReflectionClass $class, ?object $object=null): ?ReflectionProperty {
|
||||
$property = null;
|
||||
if ($class === null && $object !== null) {
|
||||
$class = new ReflectionClass($object);
|
||||
}
|
||||
if ($class !== null && $name !== null) {
|
||||
try {
|
||||
$property = $class->getProperty($name);
|
||||
$property->setAccessible(true);
|
||||
} catch (ReflectionException $e) {
|
||||
}
|
||||
}
|
||||
return $property;
|
||||
}
|
||||
|
||||
private function _setDest(?object $dest): void {
|
||||
$this->dest = $dest;
|
||||
$this->property = $this->_getProperty($this->name, null, $dest);
|
||||
}
|
||||
|
||||
function reset(?object $dest, ?string $name=null): self {
|
||||
$this->_setName($name);
|
||||
$this->_setDest($dest);
|
||||
return $this;
|
||||
}
|
||||
|
||||
function resetKey($name=null): self {
|
||||
$this->_setName($name);
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected bool $allowNull;
|
||||
|
||||
protected bool $allowFalse;
|
||||
|
||||
function exists(): bool {
|
||||
$name = $this->name;
|
||||
if ($this->dest === null) return false;
|
||||
return $name === null
|
||||
|| $this->property !== null
|
||||
|| property_exists($this->dest, $name);
|
||||
}
|
||||
|
||||
protected function _get($default=null) {
|
||||
$name = $this->name;
|
||||
$property = $this->property;
|
||||
if ($this->dest === null) {
|
||||
return $default;
|
||||
} elseif ($name === null) {
|
||||
return $this->dest;
|
||||
} elseif ($property !== null) {
|
||||
return $property->getValue($this->dest);
|
||||
} elseif (property_exists($this->dest, $name)) {
|
||||
return $this->dest->$name;
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
function available(): bool {
|
||||
if (!$this->exists()) return false;
|
||||
$value = $this->_get();
|
||||
if ($value === "") return $this->allowEmpty;
|
||||
if ($value === null) return $this->allowNull;
|
||||
if ($value === false) return $this->allowFalse;
|
||||
return true;
|
||||
}
|
||||
|
||||
function get($default=null) {
|
||||
if (!$this->exists()) return $default;
|
||||
$value = $this->_get();
|
||||
if ($value === "" && !$this->allowEmpty) return $default;
|
||||
if ($value === null && !$this->allowNull) return $default;
|
||||
if ($value === false && !$this->allowFalse) return $default;
|
||||
return $value;
|
||||
}
|
||||
|
||||
protected function _set($value): void {
|
||||
$name = $this->name;
|
||||
$property = $this->property;
|
||||
if ($this->dest === null) {
|
||||
throw StateException::unexpected_state("dest is null");
|
||||
} elseif ($name === null) {
|
||||
if (!$this->protectDest) $this->_setDest($value);
|
||||
} elseif ($property !== null) {
|
||||
$property->setValue($this->dest, $value);
|
||||
} else {
|
||||
$this->dest->$name = $value;
|
||||
}
|
||||
}
|
||||
|
||||
function set($value): void {
|
||||
$this->_set($value);
|
||||
}
|
||||
|
||||
function del(): void {
|
||||
$this->_set(null);
|
||||
}
|
||||
|
||||
function addKey($key): IAccess {
|
||||
if ($key === null) return $this;
|
||||
return new ChainAccess($this, $key);
|
||||
}
|
||||
|
||||
function ensureKeys(array $defaults, ?array $params=null): void {
|
||||
$dest = $this->dest;
|
||||
if ($dest === null) {
|
||||
# comme ne connait pas la classe de l'objet destination, on n'essaie pas
|
||||
# de le créer
|
||||
return;
|
||||
}
|
||||
$class = new ReflectionClass($dest);
|
||||
$keys = array_keys($defaults);
|
||||
$prefix = $params["key_prefix"] ?? null;
|
||||
$suffix = $params["key_suffix"] ?? null;
|
||||
foreach ($keys as $key) {
|
||||
$name = $this->_getName("$prefix$key$suffix");
|
||||
$property = $this->_getProperty($name, $class);
|
||||
if ($property !== null) {
|
||||
$type = $property->getType();
|
||||
if ($type !== null && !$property->isInitialized($dest) && $type->allowsNull()) {
|
||||
# initialiser avec null au lieu de $defaults[$key] pour respecter le
|
||||
# type de la propriété
|
||||
$property->setValue($dest, null);
|
||||
}
|
||||
} elseif (!property_exists($dest, $name)) {
|
||||
$dest->$name = $defaults[$key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\php\access;
|
||||
namespace nulib\php\access;
|
||||
|
||||
/**
|
||||
* Class ShadowAccess: accès en lecture depuis une instance de {@link IAccess}
|
||||
@ -16,6 +16,7 @@ namespace nur\sery\wip\php\access;
|
||||
*/
|
||||
class ShadowAccess extends AbstractAccess {
|
||||
function __construct(IAccess $reader, IAccess $writer) {
|
||||
parent::__construct();
|
||||
$this->reader = $reader;
|
||||
$this->writer = $writer;
|
||||
$this->getter = $reader;
|
||||
@ -27,6 +28,10 @@ class ShadowAccess extends AbstractAccess {
|
||||
|
||||
protected IGetter $getter;
|
||||
|
||||
public function isAllowEmpty(): bool {
|
||||
return $this->getter->isAllowEmpty();
|
||||
}
|
||||
|
||||
function exists(): bool {
|
||||
return $this->getter->exists();
|
||||
}
|
||||
@ -48,4 +53,20 @@ class ShadowAccess extends AbstractAccess {
|
||||
$this->writer->del();
|
||||
$this->getter = $this->reader;
|
||||
}
|
||||
|
||||
function addKey($key): IAccess {
|
||||
return new ChainAccess($this, $key);
|
||||
}
|
||||
|
||||
function ensureAssoc(array $keys, ?array $params=null): void {
|
||||
$this->writer->ensureAssoc($keys, $params);
|
||||
}
|
||||
|
||||
function ensureKeys(array $defaults, ?array $params=null): void {
|
||||
$this->writer->ensureKeys($defaults, $params);
|
||||
}
|
||||
|
||||
function ensureOrder(array $keys, ?array $params=null): void {
|
||||
$this->writer->ensureOrder($keys, $params);
|
||||
}
|
||||
}
|
||||
|
@ -1,56 +1,8 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\php\access;
|
||||
namespace nulib\php\access;
|
||||
|
||||
/**
|
||||
* Class ValueAccess: accès à une valeur unitaire
|
||||
*/
|
||||
class ValueAccess extends AbstractAccess {
|
||||
function __construct(&$dest, ?array $params=null) {
|
||||
$this->dest =& $dest;
|
||||
$this->allowNull = $params["allow_null"] ?? false;
|
||||
$this->allowFalse = $params["allow_false"] ?? true;
|
||||
$this->allowEmpty = $params["allow_empty"] ?? true;
|
||||
}
|
||||
|
||||
/** @var mixed */
|
||||
protected $dest;
|
||||
|
||||
function reset(&$dest): self {
|
||||
$this->dest =& $dest;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected bool $allowNull;
|
||||
|
||||
protected bool $allowFalse;
|
||||
|
||||
protected bool $allowEmpty;
|
||||
|
||||
function exists(): bool {
|
||||
return $this->allowNull || $this->dest !== null;
|
||||
}
|
||||
|
||||
function available(): bool {
|
||||
if (!$this->exists()) return false;
|
||||
$value = $this->dest;
|
||||
if ($value === false) return $this->allowFalse;
|
||||
if ($value === "") return $this->allowEmpty;
|
||||
return true;
|
||||
}
|
||||
|
||||
function get($default=null) {
|
||||
$value = $this->dest;
|
||||
if ($value === null && !$this->allowNull) return $default;
|
||||
if ($value === false && !$this->allowFalse) return $default;
|
||||
if ($value === "" && !$this->allowEmpty) return $default;
|
||||
return $value;
|
||||
}
|
||||
|
||||
function set($value): void {
|
||||
$this->dest = $value;
|
||||
}
|
||||
|
||||
function del(): void {
|
||||
$this->dest = null;
|
||||
}
|
||||
class ValueAccess extends KeyAccess {
|
||||
const ALLOW_NULL = false;
|
||||
const ALLOW_FALSE = true;
|
||||
const PROTECT_DEST = false;
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\php\coll;
|
||||
namespace nulib\php\coll;
|
||||
|
||||
use Iterator;
|
||||
use IteratorAggregate;
|
||||
use nulib\cl;
|
||||
use nulib\php\func;
|
||||
use nur\sery\wip\php\iter;
|
||||
use nulib\php\iter;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
@ -33,7 +34,7 @@ class Cursor implements Iterator {
|
||||
* alors retourner le tableau
|
||||
* ["a" => $row["a"], "b" => $row["x"], "c" => "y", "d" => null]
|
||||
*/
|
||||
private static function map_row(array $row, ?array $map): array {
|
||||
protected static function map_row(array $row, ?array $map): array {
|
||||
if ($map === null) return $row;
|
||||
$index = 0;
|
||||
$mapped = [];
|
||||
@ -48,7 +49,7 @@ class Cursor implements Iterator {
|
||||
$mapped[$key] = $func->invoke([$value, $key, $row]);
|
||||
} else {
|
||||
if ($value === null) $mapped[$key] = null;
|
||||
else $mapped[$key] = cl::get($row, $key);
|
||||
else $mapped[$key] = cl::get($row, $value);
|
||||
}
|
||||
}
|
||||
return $mapped;
|
||||
@ -67,7 +68,7 @@ class Cursor implements Iterator {
|
||||
* - une valeur associative $key => $value indique que la clé correspondante
|
||||
* doit exiter avec la valeur spécifiée
|
||||
*/
|
||||
private static function filter_row(array $row, $filter): bool {
|
||||
protected static function filter_row(array $row, $filter): bool {
|
||||
if ($filter === null) return false;
|
||||
if (!is_array($filter)) $filter = [$filter];
|
||||
if (!$filter) return false;
|
||||
@ -79,12 +80,12 @@ class Cursor implements Iterator {
|
||||
if (!array_key_exists($value, $row)) return false;
|
||||
} elseif (is_bool($value)) {
|
||||
if ($value) {
|
||||
if (!array_key_exists($value, $row)) return false;
|
||||
if (!array_key_exists($key, $row)) return false;
|
||||
} else {
|
||||
if (array_key_exists($value, $row)) return false;
|
||||
if (array_key_exists($key, $row)) return false;
|
||||
}
|
||||
} else {
|
||||
if (!array_key_exists($value, $row)) return false;
|
||||
if (!array_key_exists($key, $row)) return false;
|
||||
if ($row[$key] !== $value) return false;
|
||||
}
|
||||
}
|
||||
@ -114,6 +115,11 @@ class Cursor implements Iterator {
|
||||
$this->rowsGenerator = $rowsGenerator;
|
||||
$this->rowsFunc = $rowsFunc;
|
||||
|
||||
$this->cols = $params["cols"] ?? null;
|
||||
$colsFunc = $params["cols_func"] ?? null;
|
||||
if ($colsFunc !== null) $colsFunc = func::with($colsFunc);
|
||||
$this->colsFunc = $colsFunc;
|
||||
|
||||
$map = $params["map"] ?? null;
|
||||
$mapFunc = $params["map_func"] ?? null;
|
||||
if ($mapFunc !== null) {
|
||||
@ -140,16 +146,16 @@ class Cursor implements Iterator {
|
||||
/** un générateur de lignes */
|
||||
private ?Traversable $rowsGenerator;
|
||||
|
||||
/** une fonction de signature <code>function(Cursor):?iterable</code> */
|
||||
/** une fonction de signature <code>function(Cursor): ?iterable</code> */
|
||||
private ?func $rowsFunc;
|
||||
|
||||
/** une fonction de signature <code>function(Cursor):?array</code> */
|
||||
/** une fonction de signature <code>function(Cursor): ?array</code> */
|
||||
private ?func $colsFunc;
|
||||
|
||||
/** une fonction de signature <code>function(Cursor):?array</code> */
|
||||
/** une fonction de signature <code>function(Cursor): ?array</code> */
|
||||
private ?func $mapFunc;
|
||||
|
||||
/** une fonction de signature <code>function(Cursor):bool</code> */
|
||||
/** une fonction de signature <code>function(Cursor): bool</code> */
|
||||
private ?func $filterFunc;
|
||||
|
||||
protected ?iterable $rows;
|
||||
@ -173,6 +179,10 @@ class Cursor implements Iterator {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function cols(): ?array {
|
||||
return $this->row !== null? array_keys($this->row): null;
|
||||
}
|
||||
|
||||
protected function filter(): bool {
|
||||
return false;
|
||||
}
|
||||
@ -185,20 +195,24 @@ class Cursor implements Iterator {
|
||||
# Iterator
|
||||
|
||||
function rewind() {
|
||||
$this->cols = null;
|
||||
$this->index = 0;
|
||||
$this->origIndex = 0;
|
||||
$this->key = null;
|
||||
$this->raw = null;
|
||||
$this->row = null;
|
||||
if ($this->rowsGenerator !== null) {
|
||||
$this->rows = $this->rowsGenerator;
|
||||
$this->rows->rewind();
|
||||
$rows = $this->rowsGenerator;
|
||||
if ($rows instanceof IteratorAggregate) $rows = $rows->getIterator();
|
||||
$rows->rewind();
|
||||
$this->rows = $rows;
|
||||
} else {
|
||||
$this->rows = $this->rowsFunc->invoke();
|
||||
}
|
||||
}
|
||||
|
||||
function valid() {
|
||||
function valid(): bool {
|
||||
$cols = $this->colsFunc;
|
||||
$filter = $this->filterFunc;
|
||||
$map = $this->mapFunc;
|
||||
while ($valid = iter::valid($this->rows)) {
|
||||
@ -210,6 +224,10 @@ class Cursor implements Iterator {
|
||||
if (!$filtered) {
|
||||
if ($map === null) $this->row = $this->map();
|
||||
else $this->row = $map->invoke([$this]);
|
||||
if ($this->cols === null) {
|
||||
if ($cols === null) $this->cols = $this->cols();
|
||||
else $this->cols = $cols->invoke([$this]);
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
iter::next($this->rows);
|
||||
@ -219,6 +237,7 @@ class Cursor implements Iterator {
|
||||
if (!$valid) {
|
||||
iter::close($this->rows);
|
||||
$this->rows = null;
|
||||
$this->cols = null;
|
||||
$this->index = -1;
|
||||
$this->origIndex = -1;
|
||||
$this->key = null;
|
||||
@ -228,7 +247,7 @@ class Cursor implements Iterator {
|
||||
return $valid;
|
||||
}
|
||||
|
||||
function current() {
|
||||
function current(): ?array {
|
||||
return $this->row;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?php # -*- coding: utf-8 mode: php -*- vim:sw=2:sts=2:et:ai:si:sta:fenc=utf-8
|
||||
namespace nur\sery\wip\php;
|
||||
namespace nulib\php;
|
||||
|
||||
use Exception;
|
||||
use Generator;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema;
|
||||
namespace nulib\schema;
|
||||
|
||||
class OldSchema {
|
||||
/**
|
||||
|
@ -1,70 +1,60 @@
|
||||
# nur\sery\schema
|
||||
# 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 classes de ce package permettent de 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)
|
||||
les données dont on peut modéliser le schéma sont de 3 types: scalaire, tableau
|
||||
associatif ou liste. la nature du schéma (la valeur de la clé `"""`) indique le
|
||||
type de donnée modélisée
|
||||
* donnée scalaire
|
||||
|
||||
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
|
||||
forme courante:
|
||||
~~~php
|
||||
const SCALAR_SCHEMA = [
|
||||
$type, [$default, $title, ...]
|
||||
];
|
||||
~~~
|
||||
forme normalisée:
|
||||
~~~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)
|
||||
* tableau associatif
|
||||
le tableau modélisé peut avoir des clés numériques ou chaines --> seules les
|
||||
clés décrites par le schéma sont validées
|
||||
|
||||
forme courante:
|
||||
~~~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
|
||||
forme normalisée:
|
||||
~~~php
|
||||
// la valeur ci-dessus est strictement équivalent à
|
||||
const ASSOC_SCHEMA = [
|
||||
"?array",
|
||||
"" => "scalar",
|
||||
"?array", [$default, $title, ...]
|
||||
"" => "assoc",
|
||||
"schema" => [
|
||||
KEY => VALUE_SCHEMA,
|
||||
...
|
||||
],
|
||||
];
|
||||
~~~
|
||||
* liste (tableau d'éléments du même type)
|
||||
le tableau modélisé peut avoir des clés numériques ou chaines --> on ne
|
||||
modélise ni le type ni la valeur des clés
|
||||
|
||||
* 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)
|
||||
forme courante:
|
||||
~~~php
|
||||
const LIST_SCHEMA = [[
|
||||
ITEM_SCHEMA,
|
||||
]];
|
||||
~~~
|
||||
forme normalisée:
|
||||
~~~php
|
||||
const LIST_SCHEMA = [
|
||||
"?array", [$default, $title, ...]
|
||||
@ -85,7 +75,8 @@ const SCALAR_SCHEMA = [
|
||||
"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",
|
||||
"analyzer_func" => "XXX",
|
||||
"extractor_func" => "XXX",
|
||||
"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",
|
||||
@ -118,9 +109,9 @@ nature scalaire si:
|
||||
* 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`
|
||||
`messages` 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 `analyzer_func`
|
||||
~~~php
|
||||
const MESSAGE_SCHEMA = [
|
||||
"missing" => "message si la valeur n'existe pas dans la source et qu'elle est requise",
|
||||
@ -133,23 +124,27 @@ const MESSAGE_SCHEMA = [
|
||||
|
||||
## 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:
|
||||
Dans la forme courante, 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"]`
|
||||
|
||||
La forme normalisée est
|
||||
~~~php
|
||||
const ASSOC_SCHEMA = [
|
||||
"?array",
|
||||
"" => "assoc",
|
||||
"schema" => [
|
||||
KEY => VALUE_SCHEMA,
|
||||
...
|
||||
],
|
||||
];
|
||||
~~~
|
||||
le type "?array" ou "array" indique si la liste est nullable ou non. la valeur
|
||||
par défaut est "?array"
|
||||
|
||||
chaque occurrence de `KEY => VALUE_SCHEMA` définit le schéma de la valeur dont
|
||||
la clé est `KEY`
|
||||
|
||||
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
|
||||
@ -171,14 +166,11 @@ récursivement, avec cependant l'ajout de quelques clés supplémentaires:
|
||||
|
||||
## 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
|
||||
Dans la forme courante, 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]]`
|
||||
|
||||
Pour information, la forme normalisée est plutôt de la forme
|
||||
La forme normalisée est
|
||||
~~~php
|
||||
const LIST_SCHEMA = [
|
||||
"?array",
|
||||
@ -189,9 +181,4 @@ const LIST_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
|
@ -1,30 +1,60 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema;
|
||||
namespace nulib\schema;
|
||||
|
||||
use nur\sery\wip\schema\_assoc\AssocResult;
|
||||
use nur\sery\wip\schema\_list\ListResult;
|
||||
use nur\sery\wip\schema\_scalar\ScalarResult;
|
||||
use IteratorAggregate;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Class Result: 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 $messageKey clé de message si la valeur n'est pas valide
|
||||
* @property string|null $message message si la valeur n'est pas valide
|
||||
* @property Throwable|null $exception l'exception qui a fait échouer la
|
||||
* validation le cas échéant
|
||||
* @property string|null $origValue valeur originale avant extraction et analyse
|
||||
* @property mixed|null $normalizedValue la valeur normalisée si elle est
|
||||
* disponible, null sinon. ce champ est utilisé comme optimisation si la valeur
|
||||
* normalisée a déjà été calculée
|
||||
*/
|
||||
abstract class Result {
|
||||
abstract class Result implements IteratorAggregate {
|
||||
const KEYS = [
|
||||
"resultAvailable",
|
||||
"present", "available", "null", "valid", "normalized",
|
||||
"messageKey", "message", "exception",
|
||||
"origValue", "normalizedValue",
|
||||
];
|
||||
|
||||
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;
|
||||
/**
|
||||
* sélectionner le résultat associé à la clé spécifiée
|
||||
*
|
||||
* @param string|int|null $key
|
||||
* @return Result $this
|
||||
*/
|
||||
abstract function select($key): Result;
|
||||
|
||||
function getIterator() {
|
||||
foreach ($this->getKeys() as $key) {
|
||||
yield $key => $this->select($key);
|
||||
}
|
||||
$this->select(null);
|
||||
}
|
||||
|
||||
/** réinitialiser tous les objets résultats accessibles via cet objet */
|
||||
abstract function reset(): void;
|
||||
}
|
||||
|
@ -1,16 +1,48 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema;
|
||||
namespace nulib\schema;
|
||||
|
||||
use ArrayAccess;
|
||||
use nur\sery\AccessException;
|
||||
use nur\sery\cl;
|
||||
use nur\sery\wip\schema\_assoc\AssocSchema;
|
||||
use nur\sery\wip\schema\_list\ListSchema;
|
||||
use nur\sery\wip\schema\_scalar\ScalarSchema;
|
||||
use nulib\AccessException;
|
||||
use nulib\cl;
|
||||
use nulib\ref\schema\ref_schema;
|
||||
use nulib\ref\schema\ref_types;
|
||||
use nulib\schema\_assoc\AssocSchema;
|
||||
use nulib\schema\_list\ListSchema;
|
||||
use nulib\schema\_scalar\ScalarSchema;
|
||||
use nulib\schema\types\IType;
|
||||
use nulib\schema\types\tarray;
|
||||
use nulib\schema\types\tbool;
|
||||
use nulib\schema\types\tfunc;
|
||||
use nulib\schema\types\tcontent;
|
||||
use nulib\schema\types\tpkey;
|
||||
use nulib\schema\types\trawstring;
|
||||
|
||||
/**
|
||||
* Class Schema
|
||||
*
|
||||
* @property-read array|IType $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 array|null $schema
|
||||
* @property-read string|int|null $name
|
||||
* @property-read string|array|null $pkey
|
||||
* @property-read string|null $header
|
||||
* @property-read bool|null $computed
|
||||
*/
|
||||
abstract class Schema implements ArrayAccess {
|
||||
/**
|
||||
* créer si besoin une nouvelle instance de {@link Schema} à partir d'une
|
||||
* créer le cas échéant une nouvelle instance de {@link Schema} à partir d'une
|
||||
* définition de schéma
|
||||
*
|
||||
* - si $schema est une instance de schéma, la retourner
|
||||
@ -18,18 +50,18 @@ abstract class Schema implements ArrayAccess {
|
||||
* l'instance de Schema nouvelle créée
|
||||
* - sinon, prendre $definition comme définition
|
||||
*/
|
||||
static function ns(&$schema, $definition=null, $definitionKey=null): self {
|
||||
static function ns($definition=null, $definitionKey=null, &$schema=null, bool $normalize=true): self {
|
||||
if (is_array($schema)) {
|
||||
$definition = $schema;
|
||||
$schema = null;
|
||||
}
|
||||
if ($schema === null) {
|
||||
if (AssocSchema::isa_definition($definition)) {
|
||||
$schema = new AssocSchema($definition, $definitionKey);
|
||||
$schema = new AssocSchema($definition, $definitionKey, $normalize);
|
||||
} elseif (ListSchema::isa_definition($definition)) {
|
||||
$schema = new ListSchema($definition, $definitionKey);
|
||||
$schema = new ListSchema($definition, $definitionKey, $normalize);
|
||||
} elseif (ScalarSchema::isa_definition($definition)) {
|
||||
$schema = new ScalarSchema($definition, $definitionKey);
|
||||
$schema = new ScalarSchema($definition, $definitionKey, $normalize);
|
||||
} else {
|
||||
throw SchemaException::invalid_schema();
|
||||
}
|
||||
@ -38,17 +70,195 @@ abstract class Schema implements ArrayAccess {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Créer une nouvelle instance de {@link Wrapper} qui référence la
|
||||
* variable $value (si $valueKey===null) ou $value[$valueKey] si $valueKey
|
||||
* n'est pas null
|
||||
*/
|
||||
static function nv(?Value &$destv=null, &$dest=null, $destKey=null, &$schema=null, $definition=null): Value {
|
||||
static function nw(&$value=null, $valueKey=null, $definition=null, &$schema=null, ?Wrapper &$wrapper=null): Wrapper {
|
||||
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);
|
||||
return self::ns($definition, null, $schema)->getWrapper($value, $valueKey, null, $wrapper);
|
||||
}
|
||||
|
||||
protected static function have_nature(array $definition, ?string &$nature=null): bool {
|
||||
$definitionNature = $definition[""] ?? null;
|
||||
if (is_string($definitionNature)) {
|
||||
$nature = $definitionNature;
|
||||
return true;
|
||||
}
|
||||
if (is_array($definitionNature)
|
||||
&& array_key_exists(0, $definitionNature)
|
||||
&& is_string($definitionNature[0])) {
|
||||
$nature = $definitionNature;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static function _normalize_definition(&$definition, $definitionKey=null, ?array $natureMetaschema=null): void {
|
||||
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(ref_schema::VALUE_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] = ref_schema::VALUE_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;
|
||||
if ($key !== $index) {
|
||||
$definition[$index] = $definition[$key];
|
||||
unset($definition[$key]);
|
||||
}
|
||||
$index++;
|
||||
}
|
||||
}
|
||||
# type
|
||||
$types = [];
|
||||
$deftype = $definition["type"];
|
||||
$nullable = $definition["nullable"] ?? false;
|
||||
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 = trim($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);
|
||||
$natureMetaschema ??= ref_schema::NATURE_METASCHEMA;
|
||||
foreach (array_keys($natureMetaschema) as $key) {
|
||||
if (!array_key_exists($key, $nature)) {
|
||||
$nature[$key] = $natureMetaschema[$key][1];
|
||||
}
|
||||
}
|
||||
$definition[""] = $nature;
|
||||
# name, pkey, header
|
||||
$name = $definition["name"];
|
||||
$pkey = $definition["pkey"];
|
||||
$header = $definition["header"];
|
||||
if ($name === null) $name = $definitionKey;
|
||||
trawstring::ensure_nstring($name);
|
||||
tpkey::ensure_npkey($pkey);
|
||||
trawstring::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
|
||||
tarray::ensure_narray($definition["schema"]);
|
||||
trawstring::ensure_nstring($definition["title"]);
|
||||
tbool::ensure_bool($definition["required"]);
|
||||
tbool::ensure_bool($definition["nullable"]);
|
||||
tcontent::ensure_ncontent($definition["desc"]);
|
||||
tfunc::ensure_nfunc($definition["analyzer_func"]);
|
||||
tfunc::ensure_nfunc($definition["extractor_func"]);
|
||||
tfunc::ensure_nfunc($definition["parser_func"]);
|
||||
tfunc::ensure_nfunc($definition["normalizer_func"]);
|
||||
tarray::ensure_narray($definition["messages"]);
|
||||
tfunc::ensure_nfunc($definition["formatter_func"]);
|
||||
tbool::ensure_nbool($definition["computed"]);
|
||||
|
||||
switch ($nature[0] ?? null) {
|
||||
case "assoc":
|
||||
foreach ($definition["schema"] as $key => &$keydef) {
|
||||
self::_normalize_definition($keydef, $key);
|
||||
}; unset($keydef);
|
||||
break;
|
||||
case "list":
|
||||
self::_normalize_definition($definition["schema"]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected static function _ensure_nature(array $definition, string $expectedNature, ?string $expectedType=null): void {
|
||||
$nature = $definition[""];
|
||||
if (!array_key_exists(0, $nature) || $nature[0] !== $expectedNature) {
|
||||
throw SchemaException::invalid_schema("$nature: invalid nature. expected $expectedNature");
|
||||
}
|
||||
if ($expectedType !== null) {
|
||||
$types = $definition["type"];
|
||||
if (count($types) !== 1 || $types[0] !== $expectedType) {
|
||||
throw new SchemaException("{$types[O]}: invalide type. expected $expectedType");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static function _ensure_type(array &$definition): void {
|
||||
$types = $definition["type"];
|
||||
$nullable = $definition["nullable"];
|
||||
# s'il n'y a qu'une seul type, l'instancier tout de suite
|
||||
if (is_array($types) && count($types) == 1 && $types[0] !== null) {
|
||||
foreach ($types as $key => $name) {
|
||||
if ($key === 0) {
|
||||
$args = null;
|
||||
} else {
|
||||
$args = $name;
|
||||
$name = $key;
|
||||
}
|
||||
$definition["type"] = types::get($nullable, $name, $args, $definition);
|
||||
}
|
||||
}
|
||||
switch ($definition[""][0]) {
|
||||
case "assoc":
|
||||
foreach ($definition["schema"] as &$keydef) {
|
||||
self::_ensure_type($keydef);
|
||||
}; unset($keydef);
|
||||
break;
|
||||
case "list":
|
||||
self::_ensure_type($definition["schema"]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected static function _ensure_schema_instances(array &$definition): void {
|
||||
switch ($definition[""][0]) {
|
||||
case "assoc":
|
||||
foreach ($definition["schema"] as &$keydef) {
|
||||
self::_ensure_schema_instances($keydef);
|
||||
Schema::ns(null, null, $keydef, false);
|
||||
}; unset($keydef);
|
||||
break;
|
||||
case "list":
|
||||
Schema::ns(null, null, $definition["schema"], false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -57,17 +267,24 @@ abstract class Schema implements ArrayAccess {
|
||||
*/
|
||||
const SCHEMA = null;
|
||||
|
||||
/** @var array */
|
||||
protected $definition;
|
||||
protected array $_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; }
|
||||
protected array $definition;
|
||||
|
||||
abstract function newValue(?Value &$destv=null, &$dest=null, $destKey=null): Value;
|
||||
function getDefinition(): array {
|
||||
return $this->_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* retourner la liste des clés valides pour l'accès aux valeurs et résultats
|
||||
*/
|
||||
abstract function getKeys(): array;
|
||||
|
||||
abstract function getSchema($key=false): Schema;
|
||||
|
||||
abstract protected function newWrapper(): Wrapper;
|
||||
|
||||
abstract function getWrapper(&$value=null, $valueKey=null, ?array $params=null, ?Wrapper &$wrapper=null): Wrapper;
|
||||
|
||||
#############################################################################
|
||||
# key & properties
|
||||
@ -86,7 +303,15 @@ abstract class Schema implements ArrayAccess {
|
||||
throw AccessException::read_only(null, $offset);
|
||||
}
|
||||
|
||||
const _PROPERTY_PKEYS = [];
|
||||
const _PROPERTY_PKEYS = [
|
||||
"analyzerFunc" => "analyzer_func",
|
||||
"extractorFunc" => "extractor_func",
|
||||
"parserFunc" => "parser_func",
|
||||
"normalizerFunc" => "normalizer_func",
|
||||
"formatterFunc" => "formatter_func",
|
||||
"nature" => ["", 0],
|
||||
];
|
||||
|
||||
function __get($name) {
|
||||
$pkey = cl::get(static::_PROPERTY_PKEYS, $name, $name);
|
||||
return cl::pget($this->definition, $pkey);
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema;
|
||||
namespace nulib\schema;
|
||||
|
||||
use Exception;
|
||||
|
||||
|
@ -1,19 +1,50 @@
|
||||
# nur\sery\schema
|
||||
# nulib\schema
|
||||
|
||||
* l'ordre est `ensureAssoc [--> ensureKeys] [--> orderKeys]`
|
||||
* si false, supprimer la clé du tableau sauf si ensureKeys
|
||||
|
||||
* pour AssocResult, les clés suivantes sont supportées:
|
||||
* false pour la clé courante
|
||||
* null pour un résultat aggrégé
|
||||
* "" pour le résultat du tableau
|
||||
* $key pour le résultat de la clé correspondante
|
||||
|
||||
* rajouter l'attribut "size" pour spécifier la taille maximale des valeurs
|
||||
* cela pourrait servir pour générer automatiquement des tables SQL
|
||||
* ou pour modéliser un schéma FSV
|
||||
|
||||
* support allowed_values
|
||||
|
||||
* valeurs composite/computed
|
||||
* analyse / vérification de la valeur complète après calcul du résultat, si
|
||||
tous les résultats sont bons
|
||||
* calcul des valeurs composites/computed par une fonction avant/après l'analyse
|
||||
globale si résultat ok
|
||||
* fonction getter_func, setter_func, deleter_func pour les propriétés de type
|
||||
computed
|
||||
* tdate et tdatetime. qu'en est-il des autres classes (delay, etc.)
|
||||
* parse_format pour spécifier le format d'analyse au lieu de l'auto-détecter
|
||||
|
||||
* ScalarSchema::from_property()
|
||||
|
||||
* l'argument $format de AssocWrapper::format() est un tableau associatif
|
||||
`[$key => $format]`
|
||||
cela permet de spécifier des format spécifiques pour certains champs.
|
||||
* cela signifie que la valeur de retour n'est pas string :-(
|
||||
retourner string|array
|
||||
|
||||
* 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, [
|
||||
$wrapper = Schema::ns($schema, [
|
||||
"a" => "?string",
|
||||
"b" => "?int",
|
||||
])->newValue();
|
||||
$dest = ["x_a" => 5, "x_b" => "10"],
|
||||
$value->reset($dest, null, [
|
||||
])->newWrapper();
|
||||
$value = ["x_a" => 5, "x_b" => "10"],
|
||||
$wrapper->reset($value, null, [
|
||||
"key_prefix" => "x_",
|
||||
]);
|
||||
# $dest vaut ["x_a" => "5", "x_b" => 10];
|
||||
# $value 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...
|
||||
@ -21,48 +52,16 @@
|
||||
alternative: c'est lors de la *définition* du schéma que le préfixe est ajouté
|
||||
e.g
|
||||
~~~php
|
||||
$value = Schema::ns($schema, [
|
||||
$wrapper = 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];
|
||||
])->newWrapper();
|
||||
$value = ["x_a" => 5, "x_b" => "10"],
|
||||
$wrapper->reset($value);
|
||||
# $value 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
|
||||
@ -77,6 +76,8 @@
|
||||
|
||||
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"
|
||||
valeurs séquentielles sont des clés du schéma", soit un mode automatique
|
||||
activé par un paramètre où une valeur "val" devient "val"=>true si la clé
|
||||
"val" existe dans le schéma
|
||||
|
||||
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
|
@ -1,85 +0,0 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema;
|
||||
|
||||
use ArrayAccess;
|
||||
use IteratorAggregate;
|
||||
use nur\sery\wip\schema\_assoc\AssocValue;
|
||||
use nur\sery\wip\schema\_list\ListValue;
|
||||
use nur\sery\wip\schema\_scalar\ScalarValue;
|
||||
use nur\sery\wip\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();
|
||||
}
|
||||
}
|
221
src/schema/Wrapper.php
Normal file
221
src/schema/Wrapper.php
Normal file
@ -0,0 +1,221 @@
|
||||
<?php
|
||||
namespace nulib\schema;
|
||||
|
||||
use ArrayAccess;
|
||||
use IteratorAggregate;
|
||||
use nulib\php\func;
|
||||
use nulib\schema\_assoc\AssocWrapper;
|
||||
use nulib\schema\_list\ListWrapper;
|
||||
use nulib\schema\_scalar\ScalarResult;
|
||||
use nulib\schema\_scalar\ScalarWrapper;
|
||||
use nulib\schema\input\Input;
|
||||
use nulib\schema\types\IType;
|
||||
|
||||
abstract class Wrapper implements ArrayAccess, IteratorAggregate {
|
||||
protected WrapperContext $context;
|
||||
|
||||
/** changer les paramètres de gestion des valeurs */
|
||||
function resetParams(?array $params): void {
|
||||
$this->context->resetParams($params);
|
||||
}
|
||||
|
||||
protected function resetContext($resetSelectedKey): void {
|
||||
$context = $this->context;
|
||||
$type = $context->schema->type;
|
||||
if (is_array($type)) $type = $type[0];
|
||||
if (is_string($type)) $type = types::get($context->schema->nullable, $type);
|
||||
$context->type = $type;
|
||||
$context->result->reset();
|
||||
$context->analyzed = false;
|
||||
$context->normalized = false;
|
||||
if ($resetSelectedKey) $context->selectedKey = null;
|
||||
}
|
||||
|
||||
protected function afterModify(?array $params, $resetSelectedKey=false): void {
|
||||
$context = $this->context;
|
||||
$this->resetContext($resetSelectedKey);
|
||||
if ($params["analyze"] ?? $context->analyze) {
|
||||
$this->analyze($params);
|
||||
}
|
||||
if ($context->analyzed) {
|
||||
if ($params["normalize"] ?? $context->normalize) {
|
||||
$this->normalize($params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function newInput(&$value): Input {
|
||||
return new Input($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* spécifier la valeur destination gérée par cet objet.
|
||||
*
|
||||
* @param ?array $params paramètres spécifique à cet appel, qui peuvent être
|
||||
* différent des paramètres par défaut
|
||||
*/
|
||||
function reset(&$value, $valueKey=null, ?array $params=null): Wrapper {
|
||||
$context = $this->context;
|
||||
if ($value instanceof Input) $input = $value;
|
||||
else $input = $this->newInput($value);
|
||||
$context->input = $input;
|
||||
$context->valueKey = $valueKey;
|
||||
$this->afterModify($params, true);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** analyser la valeur */
|
||||
abstract static function _analyze(WrapperContext $context, Wrapper $wrapper, ?array $params): int;
|
||||
|
||||
function analyze(?array $params=null): bool {
|
||||
$context = $this->context;
|
||||
$reanalyze = $params["reanalyze"] ?? false;
|
||||
if ($context->analyzed && !$reanalyze) return false;
|
||||
|
||||
static::_analyze($context, $this, $params);
|
||||
$context->analyzed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** normaliser la valeur */
|
||||
abstract static function _normalize(WrapperContext $context, Wrapper $wrapper, ?array $params): bool;
|
||||
|
||||
function normalize(?array $params=null): bool {
|
||||
$context = $this->context;
|
||||
|
||||
// il faut que la valeur soit analysée avant de la normaliser
|
||||
static::analyze($params);
|
||||
if (!$context->analyzed) return false;
|
||||
|
||||
$renormalize = $params["renormalize"] ?? false;
|
||||
if ($renormalize || !$context->normalized) {
|
||||
$modified = static::_normalize($context, $this, $params);
|
||||
$context->normalized = true;
|
||||
} else {
|
||||
$modified = false;
|
||||
}
|
||||
|
||||
/** @var ScalarResult $result */
|
||||
$result = $context->result;
|
||||
if (!$result->valid) {
|
||||
$result->throw($params["throw"] ?? $context->throw);
|
||||
}
|
||||
return $modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir la liste des clés valides pour les valeurs accessibles via cet
|
||||
* objet
|
||||
*/
|
||||
abstract function getKeys(): array;
|
||||
|
||||
/**
|
||||
* sélectionner le wrapper associé à la clé spécifiée
|
||||
*
|
||||
* @param string|int|null $key
|
||||
* @return Wrapper $this
|
||||
*/
|
||||
abstract function select($key): Wrapper;
|
||||
|
||||
function getIterator() {
|
||||
foreach ($this->getKeys() as $key) {
|
||||
yield $key => $this->select($key);
|
||||
}
|
||||
$this->select(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* obtenir le résultat de l'analyse de la valeur du wrapper sélectionné
|
||||
*
|
||||
* cette fonction doit être appelée après {@link set()} ou {@link unset()} et
|
||||
* après que le wrapper aie été sélectionné avec {@link select()}
|
||||
*/
|
||||
function getResult($key=false): Result {
|
||||
return $this->context->result;
|
||||
}
|
||||
|
||||
/** retourner true si la valeur existe */
|
||||
function isPresent($key=false): bool {
|
||||
return $this->getResult($key)->present;
|
||||
}
|
||||
|
||||
/** retourner le type associé à la valeur */
|
||||
function getType($key=false): IType {
|
||||
return $this->context->type;
|
||||
}
|
||||
|
||||
/** retourner true si la valeur est disponible */
|
||||
function isAvailable($key=false): bool {
|
||||
return $this->getResult($key)->available;
|
||||
}
|
||||
|
||||
/** retourner true si la valeur est valide */
|
||||
function isValid($key=false): bool {
|
||||
return $this->getResult($key)->valid;
|
||||
}
|
||||
|
||||
/** retourner true si la valeur est dans sa forme normalisée */
|
||||
function isNormalized($key=false): bool {
|
||||
return $this->getResult($key)->normalized;
|
||||
}
|
||||
|
||||
function get($default=null, $key=false) {
|
||||
$context = $this->context;
|
||||
if (!$context->result->available) return $default;
|
||||
return $context->input->get($context->valueKey);
|
||||
}
|
||||
|
||||
function set($value, ?array $params=null, $key=false): self {
|
||||
$context = $this->context;
|
||||
$context->input->set($value, $context->valueKey);
|
||||
$this->afterModify($params);
|
||||
return $this;
|
||||
}
|
||||
|
||||
function unset(?array $params=null, $key=false): self {
|
||||
$context = $this->context;
|
||||
$context->input->unset($context->valueKey);
|
||||
$this->afterModify($params);
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function _format(WrapperContext $context, $format=null): string {
|
||||
$value = $context->input->get($context->valueKey);
|
||||
/** @var func $formatterFunc */
|
||||
$formatterFunc = $context->schema->formatterFunc;
|
||||
if ($formatterFunc !== null) {
|
||||
# la fonction formatter n'a pas forcément accès au format de la définition
|
||||
# le lui fournir ici
|
||||
$format ??= $context->schema->format;
|
||||
return $formatterFunc->invoke([$value, $format, $context, $this]);
|
||||
} else {
|
||||
# on assume que le type a été initialisé avec le format de la définition
|
||||
# le cas échéant
|
||||
return $context->type->format($value, $format);
|
||||
}
|
||||
}
|
||||
|
||||
/** formatter la valeur pour affichage */
|
||||
function format($format=null, $key=false): string {
|
||||
return $this->_format($this->context, $format);
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# key & properties
|
||||
|
||||
function offsetExists($offset): bool {
|
||||
return in_array($offset, $this->getKeys());
|
||||
}
|
||||
|
||||
function offsetGet($offset) {
|
||||
return $this->get(null, $offset);
|
||||
}
|
||||
|
||||
function offsetSet($offset, $value): void {
|
||||
$this->set($value, null, $offset);
|
||||
}
|
||||
|
||||
function offsetUnset($offset): void {
|
||||
$this->unset(null, $offset);
|
||||
}
|
||||
}
|
46
src/schema/WrapperContext.php
Normal file
46
src/schema/WrapperContext.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace nulib\schema;
|
||||
|
||||
use nulib\ref\schema\ref_schema;
|
||||
use nulib\schema\input\Input;
|
||||
use nulib\schema\types\IType;
|
||||
|
||||
class WrapperContext {
|
||||
|
||||
function __construct(Schema $schema, ?Input $input, $valueKey, ?array $params) {
|
||||
$this->resetParams($params);
|
||||
$this->schema = $schema;
|
||||
if ($input !== null) $this->input = $input;
|
||||
$this->valueKey = $valueKey;
|
||||
}
|
||||
|
||||
public ?array $params;
|
||||
public bool $analyze, $analyzed = false;
|
||||
public bool $normalize, $normalized = false;
|
||||
public ?bool $throw;
|
||||
|
||||
function resetParams(?array $params): void {
|
||||
$this->params = $params;
|
||||
$this->analyze = $params["analyze"] ?? ref_schema::PARAMS_SCHEMA["analyze"][1];
|
||||
$this->normalize = $params["normalize"] ?? ref_schema::PARAMS_SCHEMA["normalize"][1];
|
||||
$this->throw = $params["throw"] ?? ref_schema::PARAMS_SCHEMA["throw"][1];
|
||||
}
|
||||
|
||||
/** schéma de la valeur */
|
||||
public Schema $schema;
|
||||
/** source et destination de la valeur */
|
||||
public Input $input;
|
||||
/** @var string|int|null clé de la valeur dans le tableau destination */
|
||||
public $valueKey;
|
||||
/** @var mixed */
|
||||
public $origValue = null;
|
||||
/** @var mixed */
|
||||
public $value = null;
|
||||
|
||||
/** @var string|int|null clé sélectionnée */
|
||||
public $selectedKey = null;
|
||||
/** type de la valeur de la clé sélectionnée après analyse */
|
||||
public ?IType $type = null;
|
||||
/** résultat de l'analyse de la valeur de la clé sélectionnée */
|
||||
public ?Result $result = null;
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\_assoc;
|
||||
|
||||
use nur\sery\wip\schema\Result;
|
||||
|
||||
class AssocResult extends Result {
|
||||
function isAssoc(?AssocResult &$assoc=null): bool { $assoc = $this; return true;}
|
||||
}
|
@ -1,55 +1,96 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\_assoc;
|
||||
namespace nulib\schema\_assoc;
|
||||
|
||||
use nur\sery\cl;
|
||||
use nur\sery\ref\schema\ref_schema;
|
||||
use nur\sery\wip\schema\Schema;
|
||||
use nur\sery\wip\schema\Value;
|
||||
use nulib\cl;
|
||||
use nulib\ref\schema\ref_schema;
|
||||
use nulib\ValueException;
|
||||
use nulib\schema\Schema;
|
||||
use nulib\schema\Wrapper;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* associatif que {@link normalize_definition()} 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;
|
||||
if (self::have_nature($definition, $nature)) {
|
||||
return $nature === "assoc";
|
||||
}
|
||||
# un tableau associatif
|
||||
# tableau associatif
|
||||
return !cl::have_num_keys($definition);
|
||||
}
|
||||
|
||||
static function normalize($definition, $definitionKey=null): array {
|
||||
static function normalize_definition($definition, $definitionKey=null): array {
|
||||
if (!is_array($definition)) $definition = [$definition];
|
||||
if (!self::have_nature($definition)) {
|
||||
$definition = [
|
||||
"?array",
|
||||
"" => "assoc",
|
||||
"schema" => $definition,
|
||||
];
|
||||
}
|
||||
$natureMetaschema = array_merge(ref_schema::NATURE_METASCHEMA, ref_schema::ASSOC_NATURE_METASCHEMA);
|
||||
self::_normalize_definition($definition, $definitionKey, $natureMetaschema);
|
||||
self::_ensure_nature($definition, "assoc", "array");
|
||||
return $definition;
|
||||
}
|
||||
|
||||
function __construct($definition=null, $definitionKey=null, bool $normalize=true) {
|
||||
if ($definition === null) $definition = static::SCHEMA;
|
||||
if ($normalize) $definition = self::normalize($definition, $definitionKey);
|
||||
if ($normalize) {
|
||||
$definition = self::normalize_definition($definition, $definitionKey);
|
||||
$this->_definition = $definition;
|
||||
self::_ensure_type($definition);
|
||||
self::_ensure_schema_instances($definition);
|
||||
} else {
|
||||
# ici, $definition contient un schema déjà instancié, mais c'est le mieux
|
||||
# qu'on puisse faire
|
||||
$this->_definition = $definition;
|
||||
}
|
||||
$this->definition = $definition;
|
||||
$keys = [];
|
||||
foreach ($definition["schema"] as $key => $schema) {
|
||||
if (!$schema["computed"]) $keys[] = $key;
|
||||
}
|
||||
$this->keys = $keys;
|
||||
}
|
||||
|
||||
function isAssoc(?AssocSchema &$assoc=null): bool {
|
||||
$assoc = $this;
|
||||
return true;
|
||||
protected array $keys;
|
||||
|
||||
function getKeys(): array {
|
||||
return $this->keys;
|
||||
}
|
||||
|
||||
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));
|
||||
function getSchema($key=false): Schema {
|
||||
if ($key === null || $key === false) return $this;
|
||||
$schema = $this->definition["schema"][$key] ?? null;
|
||||
if ($schema === null) throw ValueException::invalid_key($key);
|
||||
return $schema;
|
||||
}
|
||||
|
||||
protected function newWrapper(): AssocWrapper {
|
||||
return new AssocWrapper($this);
|
||||
}
|
||||
|
||||
function getWrapper(&$value=null, $valueKey=null, ?array $params=null, ?Wrapper &$wrapper=null): AssocWrapper {
|
||||
# si pas de valeur ni de wrapper, pas d'analyse et donc pas d'exception
|
||||
# cf le code similaire dans AssocWrapper::__construct()
|
||||
$dontAnalyze = $value === null && $wrapper === null;
|
||||
if (!($wrapper instanceof AssocWrapper)) $wrapper = $this->newWrapper();
|
||||
|
||||
# la nature du schéma peut contenir des paramètres par défaut
|
||||
$nature = $this->definition[""];
|
||||
foreach (array_keys(ref_schema::ASSOC_PARAMS_SCHEMA) as $paramKey) {
|
||||
$paramValue = $nature[$paramKey] ?? null;
|
||||
if ($paramValue !== null) $params[$paramKey] = $paramValue;
|
||||
}
|
||||
if ($params !== null) $wrapper->resetParams($params);
|
||||
|
||||
return $wrapper->reset($value, $valueKey, $dontAnalyze? ["analyze" => false]: null);
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +0,0 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\_assoc;
|
||||
|
||||
use nur\sery\wip\schema\Result;
|
||||
use nur\sery\wip\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 {
|
||||
}
|
||||
}
|
185
src/schema/_assoc/AssocWrapper.php
Normal file
185
src/schema/_assoc/AssocWrapper.php
Normal file
@ -0,0 +1,185 @@
|
||||
<?php
|
||||
namespace nulib\schema\_assoc;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\ref\schema\ref_analyze;
|
||||
use nulib\ValueException;
|
||||
use nulib\schema\_scalar\ScalarResult;
|
||||
use nulib\schema\_scalar\ScalarWrapper;
|
||||
use nulib\schema\input\Input;
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\types\IType;
|
||||
use nulib\schema\Wrapper;
|
||||
use nulib\schema\WrapperContext;
|
||||
|
||||
class AssocWrapper extends Wrapper {
|
||||
function __construct(AssocSchema $schema, &$value=null, $valueKey=null, ?array $params=null) {
|
||||
$keys = $schema->getKeys();
|
||||
$keyParams = cl::merge($params, [
|
||||
"throw" => false,
|
||||
]);
|
||||
$keyWrappers = [];
|
||||
foreach ($keys as $key) {
|
||||
$value = null;
|
||||
$keyWrappers[$key] = $schema->getSchema($key)->getWrapper($value, null, $keyParams);
|
||||
}
|
||||
$this->context = $context = new AssocWrapperContext($schema, null, null, $params);
|
||||
$arrayParams = cl::merge($params, [
|
||||
"throw" => false,
|
||||
]);
|
||||
$context->arrayWrapper = new ScalarWrapper($schema, $dummy, null, $arrayParams, $context);
|
||||
$context->keys = $keys;
|
||||
$context->keyWrappers = $keyWrappers;
|
||||
|
||||
if ($value !== null) {
|
||||
# n'initialiser que si $value n'est pas null
|
||||
$this->reset($value, $valueKey);
|
||||
}
|
||||
}
|
||||
|
||||
/** @var AssocWrapperContext */
|
||||
protected WrapperContext $context;
|
||||
|
||||
protected function resetContext($resetSelectedKey): void {
|
||||
parent::resetContext($resetSelectedKey);
|
||||
$context = $this->context;
|
||||
$context->arrayWrapper->getResult()->reset();
|
||||
foreach ($context->keyWrappers as $wrapper) {
|
||||
$wrapper->getResult()->reset();
|
||||
}
|
||||
}
|
||||
|
||||
function reset(&$value, $valueKey=null, ?array $params=null): Wrapper {
|
||||
$context = $this->context;
|
||||
if ($value instanceof Input) $input = $value;
|
||||
else $input = $this->newInput($value);
|
||||
$context->input = $input;
|
||||
$context->valueKey = $valueKey;
|
||||
foreach ($context->keyWrappers as $key => $keyWrapper) {
|
||||
$keyInput = $input->addKey($valueKey);
|
||||
$keyWrapper->reset($keyInput, $key, ["analyze" => false]);
|
||||
}
|
||||
$this->afterModify($params, true);
|
||||
return $this;
|
||||
}
|
||||
|
||||
function getKeys(): array {
|
||||
return $this->context->keys;
|
||||
}
|
||||
|
||||
protected function _getWrapper($key): Wrapper {
|
||||
$context = $this->context;
|
||||
if ($key === null) return $context->arrayWrapper;
|
||||
$wrapper = $context->keyWrappers[$key] ?? null;
|
||||
if ($wrapper === null) throw ValueException::invalid_key($key);
|
||||
return $wrapper;
|
||||
}
|
||||
|
||||
/** @param string|int|null $key */
|
||||
function select($key=null): Wrapper {
|
||||
$wrapper = $this->_getWrapper($key);
|
||||
$this->context->selectedKey = $key;
|
||||
return $wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AssocWrapperContext $context
|
||||
* @param AssocWrapper $wrapper
|
||||
*/
|
||||
static function _analyze(WrapperContext $context, Wrapper $wrapper, ?array $params): int {
|
||||
if ($params["ensure_array"] ?? $context->ensureArray) {
|
||||
$valueKey = $context->valueKey;
|
||||
$array = $context->input->get($valueKey);
|
||||
if ($array === null) $context->input->set([], $valueKey);
|
||||
}
|
||||
|
||||
if ($params["ensure_assoc"] ?? $context->ensureAssoc) {
|
||||
$context->input->ensureAssoc($context->schema->getKeys());
|
||||
}
|
||||
|
||||
$what = ScalarWrapper::_analyze($context, $wrapper, $params);
|
||||
/** @var ScalarResult $result */
|
||||
$result = $context->result;
|
||||
if (!$result->valid) return $what;
|
||||
|
||||
foreach ($context->keyWrappers as $keyWrapper) {
|
||||
$keyWrapper->analyze($params);
|
||||
if (!$keyWrapper->isValid()) {
|
||||
#XXX distinguer MISSING, UNAVAILABLE, NULL et !VALID
|
||||
$what = ref_analyze::INVALID;
|
||||
$result->addInvalidMessage($keyWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
#XXX supprimer les clés "missing" ou "unavailable" sauf si $ensureKeys
|
||||
return $what;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AssocWrapperContext $context
|
||||
* @param AssocWrapper $wrapper
|
||||
*/
|
||||
static function _normalize(WrapperContext $context, Wrapper $wrapper, ?array $params): bool {
|
||||
$ensureKeys = $params["ensure_keys"] ?? $context->ensureKeys;
|
||||
$ensureOrder = $params["ensure_order"] ?? $context->ensureOrder;
|
||||
if ($ensureKeys || $ensureOrder) {
|
||||
$schema = $context->schema;
|
||||
$keys = $schema->getKeys();
|
||||
if ($ensureKeys) {
|
||||
$defaults = [];
|
||||
foreach ($keys as $key) {
|
||||
$default = $schema->getSchema($key)->default;
|
||||
if ($default === null) {
|
||||
$default = $wrapper->getType($key)->getNullValue();
|
||||
}
|
||||
$defaults[$key] = $default;
|
||||
}
|
||||
}
|
||||
if ($ensureKeys) $context->input->ensureKeys($defaults, $params);
|
||||
if ($ensureOrder) $context->input->ensureOrder($keys, $params);
|
||||
}
|
||||
|
||||
$modified = ScalarWrapper::_normalize($context, $wrapper, $params);
|
||||
foreach ($context->keyWrappers as $keyWrapper) {
|
||||
if ($keyWrapper->normalize($params)) $modified = true;
|
||||
}
|
||||
return $modified;
|
||||
}
|
||||
|
||||
function getResult($key=false): Result {
|
||||
if ($key === false) $key = $this->context->selectedKey;
|
||||
return $this->_getWrapper($key)->getResult();
|
||||
}
|
||||
|
||||
function getType($key=false): IType {
|
||||
if ($key === false) $key = $this->context->selectedKey;
|
||||
return $this->_getWrapper($key)->getType();
|
||||
}
|
||||
|
||||
function get($default=null, $key=false) {
|
||||
$context = $this->context;
|
||||
if (!$context->arrayWrapper->isAvailable()) return $default;
|
||||
if ($key === false) $key = $context->selectedKey;
|
||||
return $this->_getWrapper($key)->get($default);
|
||||
}
|
||||
|
||||
function set($value, ?array $params=null, $key=false): Wrapper {
|
||||
$context = $this->context;
|
||||
if ($key === false) $key = $context->selectedKey;
|
||||
$this->_getWrapper($key)->set($value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
function unset(?array $params=null, $key=false): Wrapper {
|
||||
$context = $this->context;
|
||||
if ($key === false) $key = $context->selectedKey;
|
||||
$this->_getWrapper($key)->unset();
|
||||
return $this;
|
||||
}
|
||||
|
||||
function format($format=null, $key=false): string {
|
||||
$context = $this->context;
|
||||
if ($key === false) $key = $context->selectedKey;
|
||||
return $this->_getWrapper($key)->format($format);
|
||||
}
|
||||
}
|
31
src/schema/_assoc/AssocWrapperContext.php
Normal file
31
src/schema/_assoc/AssocWrapperContext.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
namespace nulib\schema\_assoc;
|
||||
|
||||
use nulib\ref\schema\ref_schema;
|
||||
use nulib\schema\_scalar\ScalarWrapper;
|
||||
use nulib\schema\Wrapper;
|
||||
use nulib\schema\WrapperContext;
|
||||
|
||||
class AssocWrapperContext extends WrapperContext {
|
||||
|
||||
public bool $ensureArray;
|
||||
public bool $ensureAssoc;
|
||||
public bool $ensureKeys;
|
||||
public bool $ensureOrder;
|
||||
|
||||
public function resetParams(?array $params): void {
|
||||
parent::resetParams($params);
|
||||
$this->ensureArray = $params["ensure_array"] ?? ref_schema::ASSOC_PARAMS_SCHEMA["ensure_array"][1];
|
||||
$this->ensureAssoc = $params["ensure_assoc"] ?? ref_schema::ASSOC_PARAMS_SCHEMA["ensure_assoc"][1];
|
||||
$this->ensureKeys = $params["ensure_keys"] ?? ref_schema::ASSOC_PARAMS_SCHEMA["ensure_keys"][1];
|
||||
$this->ensureOrder = $params["ensure_order"] ?? ref_schema::ASSOC_PARAMS_SCHEMA["ensure_order"][1];
|
||||
}
|
||||
|
||||
public ?ScalarWrapper $arrayWrapper = null;
|
||||
|
||||
/** liste des clés valides */
|
||||
public array $keys;
|
||||
|
||||
/** @var Wrapper[] */
|
||||
public array $keyWrappers;
|
||||
}
|
@ -1,8 +1,51 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\_list;
|
||||
namespace nulib\schema\_list;
|
||||
|
||||
use nur\sery\wip\schema\Result;
|
||||
use nulib\ValueException;
|
||||
use nulib\schema\Result;
|
||||
|
||||
class ListResult extends Result {
|
||||
function isList(?ListResult &$list=null): bool { $list = $this; return true;}
|
||||
function __construct(Result $arrayResult, array &$keyResults) {
|
||||
$this->arrayResult = $arrayResult;
|
||||
$this->keyResults =& $keyResults;
|
||||
$this->result =& $this->arrayResult;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected Result $arrayResult;
|
||||
|
||||
/** @var Result[] */
|
||||
protected array $keyResults;
|
||||
|
||||
function getKeys(): array {
|
||||
return array_keys($this->keyResults);
|
||||
}
|
||||
|
||||
protected Result $result;
|
||||
|
||||
function select($key): Result {
|
||||
if ($key === null) {
|
||||
$this->result =& $this->arrayResult;
|
||||
} elseif (array_key_exists($key, $this->keyResults)) {
|
||||
$this->result =& $this->keyResults[$key];
|
||||
} else {
|
||||
throw ValueException::invalid_key($key);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
function reset(): void {
|
||||
$this->arrayResult->reset();
|
||||
foreach ($this->keyResults as $result) {
|
||||
$result->reset();
|
||||
}
|
||||
}
|
||||
|
||||
function __get(string $name) {
|
||||
return $this->result[$name];
|
||||
}
|
||||
|
||||
function __set(string $name, $value): void {
|
||||
$this->result[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\_list;
|
||||
namespace nulib\schema\_list;
|
||||
|
||||
use nur\sery\ref\schema\ref_schema;
|
||||
use nur\sery\wip\schema\Schema;
|
||||
use nur\sery\wip\schema\Value;
|
||||
use nulib\ref\schema\ref_schema;
|
||||
use nulib\ValueException;
|
||||
use nulib\schema\Schema;
|
||||
use nulib\schema\Wrapper;
|
||||
|
||||
class ListSchema extends Schema {
|
||||
/** @var array meta-schema d'un schéma de nature liste */
|
||||
@ -16,15 +17,8 @@ class ListSchema extends Schema {
|
||||
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;
|
||||
if (self::have_nature($definition, $nature)) {
|
||||
return $nature === "list";
|
||||
}
|
||||
# un unique élément tableau à l'index 0
|
||||
$count = count($definition);
|
||||
@ -33,21 +27,50 @@ class ListSchema extends Schema {
|
||||
}
|
||||
|
||||
static function normalize($definition, $definitionKey=null): array {
|
||||
if (!is_array($definition)) $definition = [$definition];
|
||||
if (!self::have_nature($definition)) {
|
||||
$definition = [
|
||||
"?array",
|
||||
"" => "list",
|
||||
"schema" => $definition[0],
|
||||
];
|
||||
}
|
||||
self::_normalize_definition($definition, $definitionKey);
|
||||
self::_ensure_nature($definition, "list", "array");
|
||||
return $definition;
|
||||
}
|
||||
|
||||
function __construct($definition=null, $definitionKey=null, bool $normalize=true) {
|
||||
if ($definition === null) $definition = static::SCHEMA;
|
||||
if ($normalize) $definition = self::normalize($definition, $definitionKey);
|
||||
if ($normalize) {
|
||||
$definition = self::normalize($definition, $definitionKey);
|
||||
$this->_definition = $definition;
|
||||
self::_ensure_type($definition);
|
||||
self::_ensure_schema_instances($definition);
|
||||
}
|
||||
$this->definition = $definition;
|
||||
}
|
||||
|
||||
function isList(?ListSchema &$list=null): bool {
|
||||
$list = $this;
|
||||
return true;
|
||||
const KEYS = [null];
|
||||
|
||||
function getKeys(): array {
|
||||
return self::KEYS;
|
||||
}
|
||||
|
||||
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));
|
||||
public function getSchema($key=false): Schema {
|
||||
if ($key !== null) throw ValueException::invalid_key($key);
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function newWrapper(): ListWrapper {
|
||||
return new ListWrapper($this);
|
||||
}
|
||||
|
||||
function getWrapper(&$value=null, $valueKey=null, ?array $params = null, ?Wrapper &$wrapper=null): ListWrapper {
|
||||
# si pas de valeur ni de wrapper, pas de vérification et donc pas d'exception
|
||||
# cf le code similaire dans ScalarWrapper::__construct()
|
||||
$verifix = $value !== null || $wrapper !== null;
|
||||
if (!($wrapper instanceof ListWrapper)) $wrapper = $this->newWrapper();
|
||||
return $wrapper->reset($value, $valueKey, $verifix);
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\_list;
|
||||
|
||||
use nur\sery\wip\schema\Result;
|
||||
use nur\sery\wip\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 {
|
||||
}
|
||||
}
|
17
src/schema/_list/ListWrapper.php
Normal file
17
src/schema/_list/ListWrapper.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace nulib\schema\_list;
|
||||
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Wrapper;
|
||||
|
||||
abstract/*XXX*/ class ListWrapper extends Wrapper {
|
||||
function ensureKeys(): bool {
|
||||
}
|
||||
|
||||
function orderKeys(): bool {
|
||||
}
|
||||
|
||||
/** @param Result[] $results */
|
||||
function verifix(bool $throw=true, ?array &$results=null): bool {
|
||||
}
|
||||
}
|
@ -1,40 +1,29 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\_scalar;
|
||||
namespace nulib\schema\_scalar;
|
||||
|
||||
use nur\sery\cl;
|
||||
use nur\sery\ref\schema\ref_analyze;
|
||||
use nur\sery\ref\schema\ref_schema;
|
||||
use nur\sery\ValueException;
|
||||
use nur\sery\wip\schema\Result;
|
||||
use nulib\cl;
|
||||
use nulib\ref\schema\ref_analyze;
|
||||
use nulib\ref\schema\ref_schema;
|
||||
use nulib\ValueException;
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
use nulib\schema\Wrapper;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* 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];
|
||||
return ScalarSchema::KEYS;
|
||||
}
|
||||
|
||||
function getResult($key=null): Result {
|
||||
if ($key === null) return $this;
|
||||
else throw ValueException::invalid_key($key);
|
||||
function select($key): Result {
|
||||
if ($key !== null) throw ValueException::invalid_key($key);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @var array */
|
||||
protected $result;
|
||||
protected array $result;
|
||||
|
||||
function reset(): void {
|
||||
$this->result = array_merge(
|
||||
@ -56,26 +45,13 @@ class ScalarResult extends Result {
|
||||
$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 {
|
||||
protected function getMessage(string $key, Schema $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 {
|
||||
function setMissing( Schema $schema): int {
|
||||
$this->resultAvailable = true;
|
||||
$this->present = false;
|
||||
$this->available = false;
|
||||
@ -85,14 +61,13 @@ class ScalarResult extends Result {
|
||||
$this->normalized = true;
|
||||
return ref_analyze::NORMALIZED;
|
||||
} else {
|
||||
$message = $this->getMessage("missing", $schema);
|
||||
self::replace_key($message, $schema->name);
|
||||
$this->message = $message;
|
||||
$this->messageKey = $messageKey = "missing";
|
||||
$this->message = $this->getMessage($messageKey, $schema);
|
||||
return ref_analyze::MISSING;
|
||||
}
|
||||
}
|
||||
|
||||
function setUnavailable(ScalarSchema $schema): int {
|
||||
function setUnavailable( Schema $schema): int {
|
||||
$this->resultAvailable = true;
|
||||
$this->present = true;
|
||||
$this->available = false;
|
||||
@ -102,14 +77,13 @@ class ScalarResult extends Result {
|
||||
$this->normalized = true;
|
||||
return ref_analyze::NORMALIZED;
|
||||
} else {
|
||||
$message = $this->getMessage("unavailable", $schema);
|
||||
self::replace_key($message, $schema->name);
|
||||
$this->message = $message;
|
||||
$this->messageKey = $messageKey = "unavailable";
|
||||
$this->message = $this->getMessage($messageKey, $schema);
|
||||
return ref_analyze::UNAVAILABLE;
|
||||
}
|
||||
}
|
||||
|
||||
function setNull(ScalarSchema $schema): int {
|
||||
function setNull( Schema $schema): int {
|
||||
$this->resultAvailable = true;
|
||||
$this->present = true;
|
||||
$this->available = true;
|
||||
@ -119,33 +93,60 @@ class ScalarResult extends Result {
|
||||
$this->normalized = true;
|
||||
return ref_analyze::NORMALIZED;
|
||||
} else {
|
||||
$message = $this->getMessage("null", $schema);
|
||||
self::replace_key($message, $schema->name);
|
||||
$this->message = $message;
|
||||
$this->messageKey = $messageKey = "null";
|
||||
$this->message = $this->getMessage($messageKey, $schema);
|
||||
return ref_analyze::NULL;
|
||||
}
|
||||
}
|
||||
|
||||
function setInvalid($value, ScalarSchema $schema): int {
|
||||
function setInvalid($value, Schema $schema, ?Throwable $exception=null): 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->origValue = $value;
|
||||
$this->messageKey = $messageKey = "invalid";
|
||||
$message = $this->getMessage($messageKey, $schema);
|
||||
if ($exception !== null) {
|
||||
$tmessage = ValueException::get_message($exception);
|
||||
if ($tmessage) $message = $tmessage;
|
||||
}
|
||||
$this->message = $message;
|
||||
$this->exception = $exception;
|
||||
return ref_analyze::INVALID;
|
||||
}
|
||||
|
||||
function setValid(): int {
|
||||
function addInvalidMessage(Wrapper $wrapper): void {
|
||||
$this->resultAvailable = true;
|
||||
$this->present = true;
|
||||
$this->available = true;
|
||||
$this->null = false;
|
||||
$this->valid = false;
|
||||
$this->messageKey = "invalid";
|
||||
$result = $wrapper->getResult();
|
||||
$resultException = $result->exception;
|
||||
$resultMessage = $result->message;
|
||||
if ($resultException !== null) {
|
||||
$tmessage = ValueException::get_message($resultException);
|
||||
if ($tmessage) {
|
||||
if ($resultMessage !== null) $resultMessage .= ": ";
|
||||
$resultMessage .= $tmessage;
|
||||
}
|
||||
}
|
||||
$message = $this->message;
|
||||
if ($message) $message .= "\n";
|
||||
$message .= $resultMessage;
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
function setValid($normalizedValue=null): int {
|
||||
$this->resultAvailable = true;
|
||||
$this->present = true;
|
||||
$this->available = true;
|
||||
$this->null = false;
|
||||
$this->valid = true;
|
||||
$this->normalizedValue = $normalizedValue;
|
||||
return ref_analyze::VALID;
|
||||
}
|
||||
|
||||
@ -160,6 +161,10 @@ class ScalarResult extends Result {
|
||||
}
|
||||
|
||||
function throw(bool $throw): void {
|
||||
if ($throw) throw new ValueException($this->message);
|
||||
if ($throw) {
|
||||
$exception = $this->exception;
|
||||
if ($exception !== null) throw $exception;
|
||||
else throw new ValueException($this->message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,48 +1,19 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\_scalar;
|
||||
namespace nulib\schema\_scalar;
|
||||
|
||||
use nur\sery\cl;
|
||||
use nur\sery\ref\schema\ref_schema;
|
||||
use nur\sery\ref\schema\ref_types;
|
||||
use nur\sery\wip\schema\Schema;
|
||||
use nur\sery\wip\schema\SchemaException;
|
||||
use nur\sery\wip\schema\types\tarray;
|
||||
use nur\sery\wip\schema\types\tbool;
|
||||
use nur\sery\wip\schema\types\tcallable;
|
||||
use nur\sery\wip\schema\types\tcontent;
|
||||
use nur\sery\wip\schema\types\tpkey;
|
||||
use nur\sery\wip\schema\types\tstring;
|
||||
use nur\sery\wip\schema\Value;
|
||||
use nulib\cl;
|
||||
use nulib\ref\schema\ref_schema;
|
||||
use nulib\ValueException;
|
||||
use nulib\schema\Schema;
|
||||
use nulib\schema\Wrapper;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* {@link normalize_definition()} pourrait normaliser
|
||||
*/
|
||||
static function isa_definition($definition): bool {
|
||||
# chaine ou null
|
||||
@ -71,123 +42,57 @@ class ScalarSchema extends Schema {
|
||||
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"]);
|
||||
static function normalize_definition($definition, $definitionKey=null): array {
|
||||
$natureMetaschema = array_merge(ref_schema::NATURE_METASCHEMA, ref_schema::SCALAR_NATURE_METASCHEMA);
|
||||
self::_normalize_definition($definition, $definitionKey, $natureMetaschema);
|
||||
self::_ensure_nature($definition, "scalar");
|
||||
return $definition;
|
||||
}
|
||||
|
||||
function __construct($definition=null, $definitionKey=null, bool $normalize=true) {
|
||||
if ($definition === null) $definition = static::SCHEMA;
|
||||
if ($normalize) $definition = self::normalize($definition, $definitionKey);
|
||||
if ($normalize) {
|
||||
$definition = self::normalize_definition($definition, $definitionKey);
|
||||
$this->_definition = $definition;
|
||||
self::_ensure_type($definition);
|
||||
self::_ensure_schema_instances($definition);
|
||||
} else {
|
||||
# ici, $definition contient un schema déjà instancié, mais c'est le mieux
|
||||
# qu'on puisse faire
|
||||
$this->_definition = $definition;
|
||||
}
|
||||
$this->definition = $definition;
|
||||
}
|
||||
|
||||
function isScalar(?ScalarSchema &$scalar=null): bool {
|
||||
$scalar = $this;
|
||||
return true;
|
||||
const KEYS = [null];
|
||||
|
||||
function getKeys(): array {
|
||||
return self::KEYS;
|
||||
}
|
||||
|
||||
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));
|
||||
function getSchema($key=false): Schema {
|
||||
if ($key === null || $key === false) return $this;
|
||||
throw ValueException::invalid_key($key);
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# key & properties
|
||||
protected function newWrapper(): ScalarWrapper {
|
||||
return new ScalarWrapper($this);
|
||||
}
|
||||
|
||||
const _PROPERTY_PKEYS = [
|
||||
"analyzerFunc" => "analyzer_func",
|
||||
"extractorFunc" => "extractor_func",
|
||||
"parserFunc" => "parser_func",
|
||||
"normalizerFunc" => "normalizer_func",
|
||||
"formatterFunc" => "formatter_func",
|
||||
"nature" => ["", 0],
|
||||
];
|
||||
function getWrapper(&$value=null, $valueKey=null, ?array $params=null, ?Wrapper &$wrapper=null): ScalarWrapper {
|
||||
# si pas de valeur ni de wrapper, pas de vérification et donc pas d'exception
|
||||
# cf le code similaire dans ScalarWrapper::__construct()
|
||||
$dontAnalyze = $value === null && $wrapper === null;
|
||||
if (!($wrapper instanceof ScalarWrapper)) $wrapper = $this->newWrapper();
|
||||
|
||||
# la nature du schéma peut contenir des paramètres par défaut
|
||||
$nature = $this->definition[""];
|
||||
foreach (array_keys(ref_schema::SCALAR_PARAMS_SCHEMA) as $paramKey) {
|
||||
$paramValue = $nature[$paramKey] ?? null;
|
||||
if ($paramValue !== null) $params[$paramKey] = $paramValue;
|
||||
}
|
||||
if ($params !== null) $wrapper->resetParams($params);
|
||||
|
||||
return $wrapper->reset($value, $valueKey, $dontAnalyze? ["analyze" => false]: null);
|
||||
}
|
||||
}
|
||||
|
@ -1,198 +0,0 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\_scalar;
|
||||
|
||||
use nur\sery\ref\schema\ref_analyze;
|
||||
use nur\sery\ValueException;
|
||||
use nur\sery\wip\schema\input\Input;
|
||||
use nur\sery\wip\schema\types;
|
||||
use nur\sery\wip\schema\types\IType;
|
||||
use nur\sery\wip\schema\Value;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
238
src/schema/_scalar/ScalarWrapper.php
Normal file
238
src/schema/_scalar/ScalarWrapper.php
Normal file
@ -0,0 +1,238 @@
|
||||
<?php
|
||||
namespace nulib\schema\_scalar;
|
||||
|
||||
use nulib\php\func;
|
||||
use nulib\ref\schema\ref_analyze;
|
||||
use nulib\ValueException;
|
||||
use nulib\schema\Schema;
|
||||
use nulib\schema\types;
|
||||
use nulib\schema\types\IType;
|
||||
use nulib\schema\Wrapper;
|
||||
use nulib\schema\WrapperContext;
|
||||
|
||||
/**
|
||||
* Class ScalarWrapper
|
||||
*
|
||||
* @method ScalarWrapper reset(&$value, $valueKey=null, ?array $params=null)
|
||||
* @method ScalarResult getResult($key=false)
|
||||
* @method self set($value, ?array $params=null, $key=false)
|
||||
* @method self unset(?array $params=null, $key=false)
|
||||
*/
|
||||
class ScalarWrapper extends Wrapper {
|
||||
function __construct(Schema $schema, &$value=null, $valueKey=null, ?array $params=null, ?WrapperContext $context=null) {
|
||||
if ($context === null) $context = new WrapperContext($schema, null, null, $params);
|
||||
$context->result = new ScalarResult();
|
||||
$this->context = $context;
|
||||
|
||||
if ($value !== null) {
|
||||
# n'initialiser que si $value n'est pas null
|
||||
$this->reset($value, $valueKey);
|
||||
} else {
|
||||
# il faut au moins que le type soit disponible
|
||||
$this->resetContext(false);
|
||||
}
|
||||
}
|
||||
|
||||
protected WrapperContext $context;
|
||||
|
||||
function getKeys(): array {
|
||||
return ScalarSchema::KEYS;
|
||||
}
|
||||
|
||||
/** @param string|int|null $key */
|
||||
function select($key): ScalarWrapper {
|
||||
if ($key !== null) throw ValueException::invalid_key($key);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** analyser la valeur et résoudre son type */
|
||||
protected static function _analyze0(WrapperContext $context): int {
|
||||
/** @var ScalarSchema $schema */
|
||||
$schema = $context->schema;
|
||||
$input = $context->input;
|
||||
$valueKey = $context->valueKey;
|
||||
/** @var ScalarResult $result */
|
||||
$result = $context->result;
|
||||
|
||||
$default = $schema->default;
|
||||
if (!$input->isPresent($valueKey)) {
|
||||
if ($default !== null) {
|
||||
$input->set($default, $valueKey);
|
||||
return $result->setNormalized();
|
||||
} else {
|
||||
return $result->setMissing($schema);
|
||||
}
|
||||
}
|
||||
|
||||
$schemaTypes = $schema->type;
|
||||
if ($schemaTypes instanceof IType) {
|
||||
$type = $schemaTypes;
|
||||
} else {
|
||||
# type union
|
||||
$haveType = false;
|
||||
$types = [];
|
||||
$type = $firstType = null;
|
||||
$value = null;
|
||||
# d'abord chercher un type pour lequel c'est une valeur normalisée
|
||||
$index = 0;
|
||||
$haveValue = false;
|
||||
foreach ($schemaTypes as $key => $name) {
|
||||
if ($key === $index) {
|
||||
$index++;
|
||||
$args = null;
|
||||
} else {
|
||||
$args = $name;
|
||||
$name = $key;
|
||||
}
|
||||
$type = types::get($schema->nullable, $name, $args, $schema->getDefinition());
|
||||
if ($firstType === null) $firstType = $type;
|
||||
$types[] = $type;
|
||||
if ($type->isAvailable($input, $valueKey)) {
|
||||
if (!$haveValue) {
|
||||
$value = $input->get($valueKey);
|
||||
$haveValue = true;
|
||||
}
|
||||
if ($type->isValid($value, $normalized) && $normalized) {
|
||||
$haveType = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
# ensuite chercher un type pour lequel la valeur est valide
|
||||
if (!$haveType) {
|
||||
foreach ($types as $type) {
|
||||
if ($type->isAvailable($input, $valueKey) && $type->isValid($value)) {
|
||||
$haveType = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
# sinon prendre le premier type
|
||||
if (!$haveType) {
|
||||
$type = $firstType;
|
||||
}
|
||||
}
|
||||
$context->type = $type;
|
||||
|
||||
if (!$type->isAvailable($input, $valueKey)) {
|
||||
if ($default !== null) {
|
||||
$input->set($default, $valueKey);
|
||||
return $result->setNormalized();
|
||||
} else {
|
||||
return $result->setUnavailable($schema);
|
||||
}
|
||||
}
|
||||
|
||||
$value = $input->get($valueKey);
|
||||
$context->origValue = $context->value = $value;
|
||||
if ($type->isNull($value)) {
|
||||
return $result->setNull($schema);
|
||||
} elseif (is_string($value)) {
|
||||
return ref_analyze::STRING;
|
||||
} elseif ($type->isValid($value, $normalized)) {
|
||||
if ($normalized) return $result->setNormalized();
|
||||
else return $result->setValid();
|
||||
} else {
|
||||
return $result->setInvalid($value, $schema);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ScalarWrapper $wrapper
|
||||
*/
|
||||
static function _analyze(WrapperContext $context, Wrapper $wrapper, ?array $params): int {
|
||||
/** @var ScalarSchema $schema */
|
||||
$schema = $context->schema;
|
||||
$input = $context->input;
|
||||
$valueKey = $context->valueKey;
|
||||
/** @var ScalarResult $result */
|
||||
$result = $context->result;
|
||||
|
||||
/** @var func $analyzerFunc */
|
||||
$analyzerFunc = $schema->analyzerFunc;
|
||||
if ($analyzerFunc !== null) $what = $analyzerFunc->invoke([$context, $wrapper]);
|
||||
else $what = self::_analyze0($context);
|
||||
if ($what !== ref_analyze::STRING) return $what;
|
||||
|
||||
$value = $context->value;
|
||||
try {
|
||||
/** @var func $extractorFunc */
|
||||
$extractorFunc = $schema->extractorFunc;
|
||||
if ($extractorFunc !== null) $extracted = $extractorFunc->invoke([$value, $context, $wrapper]);
|
||||
else $extracted = $context->type->extract($value);
|
||||
$context->value = $extracted;
|
||||
} catch (ValueException $e) {
|
||||
return $result->setInvalid($context->origValue, $schema, $e);
|
||||
}
|
||||
if ($context->type->isNull($extracted)) return $result->setNull($schema);
|
||||
|
||||
try {
|
||||
/** @var func $parserFunc */
|
||||
$parserFunc = $schema->parserFunc;
|
||||
if ($parserFunc !== null) $parsed = $parserFunc->invoke([$extracted, $context, $wrapper]);
|
||||
else $parsed = $context->type->parse($extracted);
|
||||
$context->value = $parsed;
|
||||
} catch (ValueException $e) {
|
||||
return $result->setInvalid($context->origValue, $schema, $e);
|
||||
}
|
||||
|
||||
$normalized = $parsed === $context->origValue;
|
||||
if ($normalized) {
|
||||
$input->set($parsed, $valueKey);
|
||||
return $result->setNormalized();
|
||||
} else {
|
||||
$input->set($extracted, $valueKey);
|
||||
return $result->setValid($parsed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ScalarWrapper $wrapper
|
||||
*/
|
||||
static function _normalize(WrapperContext $context, Wrapper $wrapper, ?array $params): bool {
|
||||
/** @var ScalarSchema $schema */
|
||||
$schema = $context->schema;
|
||||
$input = $context->input;
|
||||
$valueKey = $context->valueKey;
|
||||
/** @var ScalarResult $result */
|
||||
$result = $context->result;
|
||||
|
||||
$normalize = false;
|
||||
$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
|
||||
$input->set(null, $valueKey);
|
||||
} elseif ($result->valid && !$result->normalized) {
|
||||
$normalizedValue = $result->normalizedValue;
|
||||
if ($normalizedValue !== null) {
|
||||
# la valeur normalisée est disponible
|
||||
$input->set($normalizedValue, $valueKey);
|
||||
$result->normalizedValue = null;
|
||||
$modified = true;
|
||||
} else {
|
||||
# normaliser la valeur
|
||||
$normalize = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$normalize = true;
|
||||
}
|
||||
|
||||
if ($normalize) {
|
||||
$value = $input->get($valueKey);
|
||||
/** @var func $normalizerFunc */
|
||||
$normalizerFunc = $schema->normalizerFunc;
|
||||
if ($normalizerFunc !== null) {
|
||||
$orig = $value;
|
||||
$value = $normalizerFunc->invoke([$orig, $context, $wrapper]);
|
||||
$modified = $value !== $orig;
|
||||
} else {
|
||||
$modified = $context->type->normalize($value, $result, $schema);
|
||||
}
|
||||
if ($result->valid) $input->set($value, $valueKey);
|
||||
}
|
||||
return $modified;
|
||||
}
|
||||
}
|
@ -1,12 +1,11 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\input;
|
||||
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
|
||||
use nur\sery\wip\php\access\FormAccess;
|
||||
use nur\sery\wip\php\access\IAccess;
|
||||
use nur\sery\wip\php\access\KeyAccess;
|
||||
use nur\sery\wip\php\access\ShadowAccess;
|
||||
use nulib\php\access\FormAccess;
|
||||
use nulib\php\access\IAccess;
|
||||
use nulib\php\access\ShadowAccess;
|
||||
|
||||
/**
|
||||
* Class FormInput: accès à des paramètres de formulaire (POST ou GET, dans cet
|
||||
@ -18,15 +17,14 @@ use nur\sery\wip\php\access\ShadowAccess;
|
||||
class FormInput extends Input {
|
||||
const ALLOW_EMPTY = false;
|
||||
|
||||
protected function formAccess($key): IAccess {
|
||||
return new FormAccess($key, [
|
||||
"allow_empty" => $this->allowEmpty,
|
||||
]);
|
||||
function __construct(&$dest=null, ?array $params=null) {
|
||||
parent::__construct($dest, $params);
|
||||
$this->access = new ShadowAccess($this->formAccess($this->access), $this->access);
|
||||
}
|
||||
|
||||
protected function access($key): IAccess {
|
||||
return $this->keyAccess[$key] ??= new ShadowAccess($this->formAccess($key), new KeyAccess($this->dest, $key, [
|
||||
"allow_empty" => $this->allowEmpty,
|
||||
]));
|
||||
protected function formAccess(IAccess $access): IAccess {
|
||||
return new FormAccess(null, [
|
||||
"allow_empty" => $access->isAllowEmpty(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\input;
|
||||
namespace nulib\schema\input;
|
||||
|
||||
use nur\sery\wip\php\access\GetAccess;
|
||||
use nur\sery\wip\php\access\IAccess;
|
||||
use nulib\php\access\GetAccess;
|
||||
use nulib\php\access\IAccess;
|
||||
|
||||
/**
|
||||
* Class GetInput: accès à des paramètres de formulaire de type GET uniquement
|
||||
@ -11,9 +11,9 @@ use nur\sery\wip\php\access\IAccess;
|
||||
* une référence
|
||||
*/
|
||||
class GetInput extends FormInput {
|
||||
protected function formAccess($key): IAccess {
|
||||
return new GetAccess($key, [
|
||||
"allow_empty" => $this->allowEmpty,
|
||||
protected function formAccess(IAccess $access): IAccess {
|
||||
return new GetAccess(null, [
|
||||
"allow_empty" => $access->isAllowEmpty(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\input;
|
||||
namespace nulib\schema\input;
|
||||
|
||||
use nur\sery\wip\php\access\IAccess;
|
||||
use nur\sery\wip\php\access\KeyAccess;
|
||||
use nur\sery\wip\php\access\ValueAccess;
|
||||
use nulib\ref\schema\ref_input;
|
||||
use nulib\StateException;
|
||||
use nulib\php\access\IAccess;
|
||||
use nulib\php\access\KeyAccess;
|
||||
use nulib\php\access\PropertyAccess;
|
||||
|
||||
/**
|
||||
* Class Input: accès à une valeur
|
||||
@ -13,54 +15,72 @@ use nur\sery\wip\php\access\ValueAccess;
|
||||
class Input {
|
||||
const ALLOW_EMPTY = true;
|
||||
|
||||
function __construct(&$dest=null, ?array $params=null) {
|
||||
$this->dest =& $dest;
|
||||
$this->allowEmpty = $params["allow_empty"] ?? static::ALLOW_EMPTY;
|
||||
private static function unexpected_access_type(): StateException {
|
||||
return StateException::unexpected_state("access_type");
|
||||
}
|
||||
|
||||
/** @var mixed */
|
||||
protected $dest;
|
||||
function __construct(&$dest=null, ?array $params=null) {
|
||||
$accessType = $params["access_type"] ?? ref_input::ACCESS_AUTO;
|
||||
if ($accessType === ref_input::ACCESS_AUTO) {
|
||||
$accessType = is_object($dest)? ref_input::ACCESS_PROPERTY : ref_input::ACCESS_KEY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var bool comment considérer une chaine vide: "" si allowEmpty, null sinon
|
||||
*/
|
||||
protected $allowEmpty;
|
||||
|
||||
protected ?ValueAccess $valueAccess = null;
|
||||
protected ?array $keyAccess = null;
|
||||
|
||||
protected function access($key): IAccess {
|
||||
if ($key === null) {
|
||||
return $this->valueAccess ??= new ValueAccess($this->dest, [
|
||||
$allowEmpty = $params["allow_empty"] ?? static::ALLOW_EMPTY;
|
||||
if ($accessType == ref_input::ACCESS_PROPERTY) {
|
||||
$this->access = new PropertyAccess($dest, null, [
|
||||
"allow_empty" => $allowEmpty,
|
||||
"allow_null" => true,
|
||||
]);
|
||||
} elseif ($accessType == ref_input::ACCESS_KEY) {
|
||||
$this->access = new KeyAccess($dest, null, [
|
||||
"allow_empty" => $allowEmpty,
|
||||
"allow_null" => true,
|
||||
"allow_empty" => $this->allowEmpty,
|
||||
]);
|
||||
} else {
|
||||
return $this->keyAccess[$key] ??= new KeyAccess($this->dest, $key, [
|
||||
"allow_empty" => $this->allowEmpty,
|
||||
]);
|
||||
throw self::unexpected_access_type();
|
||||
}
|
||||
}
|
||||
|
||||
protected IAccess $access;
|
||||
|
||||
/** tester si la valeur existe sans tenir compte de $allowEmpty */
|
||||
function isPresent($key=null): bool {
|
||||
return $this->access($key)->exists();
|
||||
return $this->access->resetKey($key)->exists();
|
||||
}
|
||||
|
||||
/** tester si la valeur est disponible en tenant compte de $allowEmpty */
|
||||
function isAvailable($key=null): bool {
|
||||
return $this->access($key)->available();
|
||||
return $this->access->resetKey($key)->available();
|
||||
}
|
||||
|
||||
function get($key=null) {
|
||||
return $this->access($key)->get();
|
||||
return $this->access->resetKey($key)->get();
|
||||
}
|
||||
|
||||
function set($value, $key=null): void {
|
||||
$this->access($key)->set($value);
|
||||
$this->access->resetKey($key)->set($value);
|
||||
}
|
||||
|
||||
function unset($key=null): void {
|
||||
$this->access($key)->del();
|
||||
$this->access->resetKey($key)->del();
|
||||
}
|
||||
|
||||
function addKey($key): Input {
|
||||
if ($key === null) return $this;
|
||||
$input = clone $this;
|
||||
$input->access = $this->access->addKey($key);
|
||||
return $input;
|
||||
}
|
||||
|
||||
function ensureAssoc(array $keys, ?array $params=null): void {
|
||||
$this->access->ensureAssoc($keys, $params);
|
||||
}
|
||||
|
||||
function ensureKeys(array $defaults, ?array $params=null): void {
|
||||
$this->access->ensureKeys($defaults, $params);
|
||||
}
|
||||
|
||||
function ensureOrder(array $keys, ?array $params=null): void {
|
||||
$this->access->ensureOrder($keys, $params);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\input;
|
||||
namespace nulib\schema\input;
|
||||
|
||||
use nur\sery\wip\php\access\IAccess;
|
||||
use nur\sery\wip\php\access\PostAccess;
|
||||
use nulib\php\access\IAccess;
|
||||
use nulib\php\access\PostAccess;
|
||||
|
||||
/**
|
||||
* Class PostInput: accès à des paramètres de formulaire de type POST uniquement
|
||||
@ -11,9 +11,9 @@ use nur\sery\wip\php\access\PostAccess;
|
||||
* une référence
|
||||
*/
|
||||
class PostInput extends FormInput {
|
||||
protected function formAccess($key): IAccess {
|
||||
return new PostAccess($key, [
|
||||
"allow_empty" => $this->allowEmpty,
|
||||
protected function formAccess(IAccess $access): IAccess {
|
||||
return new PostAccess(null, [
|
||||
"allow_empty" => $access->isAllowEmpty(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,22 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema;
|
||||
namespace nulib\schema;
|
||||
|
||||
use nur\sery\wip\schema\types\IType;
|
||||
use nur\sery\wip\schema\types\Registry;
|
||||
use nur\sery\wip\schema\types\tarray;
|
||||
use nur\sery\wip\schema\types\tbool;
|
||||
use nur\sery\wip\schema\types\tcallable;
|
||||
use nur\sery\wip\schema\types\tfloat;
|
||||
use nur\sery\wip\schema\types\tint;
|
||||
use nur\sery\wip\schema\types\tstring;
|
||||
use nulib\ValueException;
|
||||
use nulib\schema\types\IType;
|
||||
use nulib\schema\types\Registry;
|
||||
use nulib\schema\types\tarray;
|
||||
use nulib\schema\types\tbool;
|
||||
use nulib\schema\types\tfunc;
|
||||
use nulib\schema\types\tcontent;
|
||||
use nulib\schema\types\tfloat;
|
||||
use nulib\schema\types\tint;
|
||||
use nulib\schema\types\tkey;
|
||||
use nulib\schema\types\tmixed;
|
||||
use nulib\schema\types\tpkey;
|
||||
use nulib\schema\types\traw;
|
||||
use nulib\schema\types\trawstring;
|
||||
use nulib\schema\types\tstring;
|
||||
use nulib\schema\types\ttext;
|
||||
|
||||
/**
|
||||
* Class types: classe outil pour gérer le registre de types
|
||||
@ -24,14 +32,25 @@ class types {
|
||||
return self::$registry;
|
||||
}
|
||||
|
||||
static function get(string $name): IType {
|
||||
return self::registry()->get($name);
|
||||
static function get(bool $nullable, $name, ?array $args=null, ?array $definition=null): IType {
|
||||
if ($name instanceof IType) return $name;
|
||||
if ($name !== null && !is_string($name)) {
|
||||
throw ValueException::invalid_type($name, "string");
|
||||
}
|
||||
return self::registry()->get($nullable, $name, $args, $definition);
|
||||
}
|
||||
|
||||
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"); }
|
||||
static function rawstring(bool $nullable=true): trawstring { return self::get($nullable, "rawstring"); }
|
||||
static function string(bool $nullable=true): tstring { return self::get($nullable, "string"); }
|
||||
static function text(bool $nullable=true): ttext { return self::get($nullable, "text"); }
|
||||
static function bool(bool $nullable=true): tbool { return self::get($nullable, "bool"); }
|
||||
static function int(bool $nullable=true): tint { return self::get($nullable, "int"); }
|
||||
static function float(bool $nullable=true): tfloat { return self::get($nullable, "float"); }
|
||||
static function array(bool $nullable=true): tarray { return self::get($nullable, "array"); }
|
||||
static function callable(bool $nullable=true): tfunc { return self::get($nullable, "callable"); }
|
||||
static function raw(bool $nullable=true): traw { return self::get($nullable, "raw"); }
|
||||
static function mixed(bool $nullable=true): tmixed { return self::get($nullable, "mixed"); }
|
||||
static function key(bool $nullable=true): tkey { return self::get($nullable, "key"); }
|
||||
static function pkey(bool $nullable=true): tpkey { return self::get($nullable, "pkey"); }
|
||||
static function content(bool $nullable=true): tcontent { return self::get($nullable, "content"); }
|
||||
}
|
||||
|
@ -1,33 +1,147 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\types;
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nur\sery\wip\schema\input\Input;
|
||||
use nur\sery\wip\schema\Result;
|
||||
use nur\sery\wip\schema\Schema;
|
||||
use nulib\ValueException;
|
||||
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;
|
||||
static function get_params_from_definition(?array $definition): ?array;
|
||||
|
||||
/**
|
||||
* obtenir, pour information, le nom officiel de ce type, utilisable dans une
|
||||
* définition de schéma
|
||||
*/
|
||||
function getName(): string;
|
||||
|
||||
/** obtenir la liste des aliases valides pour ce type */
|
||||
function getAliases(): array;
|
||||
|
||||
/**
|
||||
* @return string la classe des objets gérés par ce format: le type attendu
|
||||
* par {@link format()} et le type retourné par {@link normalize()}
|
||||
*
|
||||
* Les valeurs "mixed", "bool", "float", "int", "string" et "array" peuvent
|
||||
* aussi être retournées, bien qu'elles ne soient pas à proprement parler des
|
||||
* classes.
|
||||
*
|
||||
* La valeur "mixed" signifie qu'il peut s'agir de n'importe quelle classe
|
||||
* et/ou que la valeur ne peut pas être déterminée à l'avance.
|
||||
*/
|
||||
function getClass(): string;
|
||||
|
||||
/**
|
||||
* comme {@link getClass()} mais peut être utilisé directement comme type dans
|
||||
* une déclaration PHP. notamment, si le type est nullable et que
|
||||
* $allowNullable==true, il y a "?" devant le nom.
|
||||
*
|
||||
* Par exemple:
|
||||
* - pour un type chaine nullable, {@link getClass()} retournerait "string"
|
||||
* alors que cette méthode retournerait "?string".
|
||||
* - pour un type mixed, {@link getClass()} retournerait "mixed" alors que
|
||||
* cette méthode retournerait null
|
||||
*/
|
||||
function getPhpType(bool $allowNullable=true): ?string;
|
||||
|
||||
/** obtenir la valeur "nulle" pour les objets de ce type */
|
||||
function getNullValue();
|
||||
|
||||
/**
|
||||
* indiquer si c'est le type d'une valeur qui ne peut prendre que 2 états: une
|
||||
* "vraie" et une "fausse"
|
||||
*/
|
||||
function is2States(): bool;
|
||||
|
||||
/**
|
||||
* Si {@link is2States()} est vrai, retourner les deux valeurs [faux, vrai]
|
||||
*/
|
||||
function get2States(): array;
|
||||
|
||||
/**
|
||||
* indiquer si c'est le type d'une valeur qui ne peut prendre que 3 états: une
|
||||
* "vraie", une "fausse", et une "indéterminée"
|
||||
*/
|
||||
function is3States(): bool;
|
||||
|
||||
/**
|
||||
* Si {@link is3States()} est vrai, retourner les 3 valeurs [faux, vrai, undef]
|
||||
*/
|
||||
function get3States(): array;
|
||||
|
||||
/** la donnée $input($valueKey) est-elle disponible? */
|
||||
function isAvailable(Input $input, $valueKey): bool;
|
||||
|
||||
/** la valeur $value est-elle nulle? */
|
||||
function isNull($value): bool;
|
||||
|
||||
/** la valeur $value est-elle valide et normalisée le cas échéant? */
|
||||
/**
|
||||
* la valeur $value est-elle valide et normalisée le cas échéant?
|
||||
*
|
||||
* NB: si $value est un string. elle doit avoir déjà été traitée au préalable
|
||||
* par extract() et parse()
|
||||
*/
|
||||
function isValid($value, ?bool &$normalized=null): bool;
|
||||
|
||||
/**
|
||||
* analyser, corriger éventuellement et normaliser la valeur
|
||||
* extraire de la chaine la valeur à analyser
|
||||
*
|
||||
* si la valeur était déjà normalisée, ou si une erreur s'est produite,
|
||||
* retourner false.
|
||||
* @throws ValueException en cas d'erreur d'analyse
|
||||
*/
|
||||
function verifix(&$value, Result &$result, Schema $schema): bool;
|
||||
function extract(string $value): string;
|
||||
|
||||
/**
|
||||
* formatter la valeur pour affichage. $value est garanti d'être du bon type
|
||||
* analyser la chaine et retourner la valeur "convertie"
|
||||
*
|
||||
* @throws ValueException en cas d'erreur d'analyse
|
||||
*/
|
||||
function parse(string $value);
|
||||
|
||||
/**
|
||||
* normaliser la valeur. elle *doit* déjà être valide.
|
||||
* Si $value est un string. elle *doit* avoir déjà été traitée au préalable
|
||||
* par extract() et parse()
|
||||
*
|
||||
* - si $result indique que la valeur est déjà normalisée, cette méthode ne
|
||||
* fait rien
|
||||
* - si la valeur était déjà normalisée, mettre à jour $result pour indiquer
|
||||
* que la valeur est normalisée et retourner false
|
||||
* - sinon, retourner true pour indiquer qu'il a fallut normaliser la valeur.
|
||||
* $result n'est pas modifié
|
||||
*/
|
||||
function normalize(&$value, Result $result, Schema $schema): bool;
|
||||
|
||||
/**
|
||||
* formatter la valeur pour affichage. si $value n'est pas null, elle est
|
||||
* garantie d'être du bon type
|
||||
*/
|
||||
function format($value, $format=null): string;
|
||||
|
||||
#############################################################################
|
||||
|
||||
/** @return string le nom d'un getter pour une valeur de ce type */
|
||||
function getGetterName(string $name): string;
|
||||
|
||||
/** @return string le nom d'un setter pour une valeur de ce type */
|
||||
function getSetterName(string $name): string;
|
||||
|
||||
/** @return string le nom d'un deleter pour une valeur de ce type */
|
||||
function getDeleterName(string $name): string;
|
||||
|
||||
/**
|
||||
* @return string le nom d'une constante de classe pour une valeur de ce type
|
||||
*/
|
||||
function getClassConstName(string $name): string;
|
||||
|
||||
/**
|
||||
* @return string le nom d'une propriété d'une classe pour une valeur de ce
|
||||
* type
|
||||
*/
|
||||
function getObjectPropertyName(string $name): string;
|
||||
|
||||
/** @return string le nom d'une clé d'un tableau pour une valeur de ce type */
|
||||
function getArrayKeyName(string $name): string;
|
||||
}
|
||||
|
@ -1,18 +1,23 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\types;
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nur\sery\cl;
|
||||
use nulib\cl;
|
||||
use nulib\php\func;
|
||||
use nulib\ValueException;
|
||||
|
||||
class Registry {
|
||||
const TYPES = [
|
||||
"rawstring" => trawstring::class,
|
||||
"string" => tstring::class,
|
||||
"text" => ttext::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,
|
||||
"float" => tfloat::class, "flt" => tfloat::class, "double" => tfloat::class, "dbl" => tfloat::class,
|
||||
"array" => tarray::class,
|
||||
"callable" => tcallable::class,
|
||||
"func" => tfunc::class, "function" => tfunc::class, "callable" => tfunc::class,
|
||||
# types spéciaux
|
||||
"raw" => traw::class,
|
||||
"mixed" => tmixed::class,
|
||||
"key" => tkey::class,
|
||||
"pkey" => tpkey::class,
|
||||
"content" => tcontent::class,
|
||||
@ -25,12 +30,33 @@ class Registry {
|
||||
/** @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();
|
||||
function get(bool $nullable, ?string $name, ?array $args=null, ?array $definition=null): IType {
|
||||
if (cl::is_list($args)) {
|
||||
$key = array_key_last($args);
|
||||
$params = $args[$key];
|
||||
unset($args[$key]);
|
||||
} else {
|
||||
$params = $args;
|
||||
$args = null;
|
||||
}
|
||||
$name ??= "raw";
|
||||
$class = cl::get(self::TYPES, $name);
|
||||
if ($class === null) {
|
||||
$class = $name;
|
||||
if (!class_exists($class)) {
|
||||
throw ValueException::invalid_type($class, IType::class);
|
||||
} elseif (!is_subclass_of($class, IType::class)) {
|
||||
return new tgeneric($class, $nullable, $params);
|
||||
}
|
||||
}
|
||||
$params = cl::merge($class::get_params_from_definition($definition), $params);
|
||||
if ($args || $params !== null) {
|
||||
$args ??= [];
|
||||
return func::with([$class, false, ...$args, $nullable, $params])->invoke();
|
||||
}
|
||||
if ($nullable) $name = "?$name";
|
||||
$type = cl::get($this->types, $name);
|
||||
if ($type === null) $type = $this->types[$name] = new $class($nullable);
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
19
src/schema/types/_tformatable.php
Normal file
19
src/schema/types/_tformatable.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
abstract class _tformatable extends _tsimple {
|
||||
const FORMAT = null;
|
||||
|
||||
static function get_params_from_definition(?array $definition): ?array {
|
||||
$params = null;
|
||||
$format = $definition["format"] ?? null;
|
||||
if ($format !== null) $params["format"] = $format;
|
||||
return $params;
|
||||
}
|
||||
|
||||
function format($value, $format=null): string {
|
||||
$format ??= $this->params["format"] ?? static::FORMAT;
|
||||
if ($format !== null) return sprintf($format, $value);
|
||||
else return strval($value);
|
||||
}
|
||||
}
|
@ -1,14 +1,99 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\types;
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nur\sery\wip\schema\input\Input;
|
||||
use nulib\StateException;
|
||||
use nur\prop;
|
||||
use nulib\schema\input\Input;
|
||||
use nur\str;
|
||||
|
||||
abstract class _tsimple implements IType {
|
||||
function isAvailable(Input $input, $destKey): bool {
|
||||
return $input->isAvailable($destKey) && $input->get($destKey) !== false;
|
||||
const NAME = null;
|
||||
|
||||
const ALIASES = [];
|
||||
|
||||
static function get_params_from_definition(?array $definition): ?array {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* $nullable et $params doivent toujours être les derniers arguments du
|
||||
* constructeur
|
||||
*/
|
||||
function __construct(bool $nullable, ?array $params=null) {
|
||||
$this->nullable = $nullable;
|
||||
$this->params = $params;
|
||||
}
|
||||
|
||||
protected bool $nullable;
|
||||
|
||||
protected ?array $params;
|
||||
|
||||
function getName(): string {
|
||||
return static::NAME;
|
||||
}
|
||||
|
||||
function getAliases(): array {
|
||||
return static::ALIASES;
|
||||
}
|
||||
|
||||
function getPhpType(bool $allowNullable=true): ?string {
|
||||
$phpType = $this->getClass();
|
||||
if ($phpType === "mixed") return null;
|
||||
if ($this->nullable && $allowNullable) $phpType = "?$phpType";
|
||||
return $phpType;
|
||||
}
|
||||
|
||||
function is2States(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
function get2States(): array {
|
||||
throw StateException::not_implemented();
|
||||
}
|
||||
|
||||
function is3States(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
function get3States(): array {
|
||||
throw StateException::not_implemented();
|
||||
}
|
||||
|
||||
function isAvailable(Input $input, $valueKey): bool {
|
||||
return $input->isAvailable($valueKey) && $input->get($valueKey) !== false;
|
||||
}
|
||||
|
||||
function isNull($value): bool {
|
||||
return $value === null || (is_string($value) && trim($value) === "");
|
||||
return $value === null || $value === "";
|
||||
}
|
||||
|
||||
function extract(string $value): string {
|
||||
return $value;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
function getGetterName(string $name): string {
|
||||
return prop::get_getter_name($name);
|
||||
}
|
||||
|
||||
function getSetterName(string $name): string {
|
||||
return prop::get_setter_name($name);
|
||||
}
|
||||
|
||||
function getDeleterName(string $name): string {
|
||||
return prop::get_deletter_name($name);
|
||||
}
|
||||
|
||||
function getClassConstName(string $name): string {
|
||||
return strtoupper($name);
|
||||
}
|
||||
|
||||
function getObjectPropertyName(string $name): string {
|
||||
return str::us2camel($name);
|
||||
}
|
||||
|
||||
function getArrayKeyName(string $name): string {
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
26
src/schema/types/_tstring.php
Normal file
26
src/schema/types/_tstring.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nulib\str;
|
||||
|
||||
abstract class _tstring extends _tsimple {
|
||||
/** @var bool faut-il trimmer la valeur */
|
||||
const TRIM = false;
|
||||
/** @var bool faut-il normaliser les caractères fin de ligne */
|
||||
const NORM_NL = false;
|
||||
|
||||
static function get_params_from_definition(?array $definition): ?array {
|
||||
$params = null;
|
||||
$trim = $definition["trim"] ?? null;
|
||||
if ($trim !== null) $params["trim"] = $trim;
|
||||
$normNl = $definition["norm_nl"] ?? null;
|
||||
if ($normNl !== null) $params["norm_nl"] = $normNl;
|
||||
return $params;
|
||||
}
|
||||
|
||||
function extract(string $value): string {
|
||||
if ($this->params["trim"] ?? static::TRIM) $value = trim($value);
|
||||
if ($this->params["norm_nl"] ?? static::NORM_NL) $value = str::norm_nl($value);
|
||||
return $value;
|
||||
}
|
||||
}
|
10
src/schema/types/_tunion.php
Normal file
10
src/schema/types/_tunion.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
abstract class _tunion extends _tsimple {
|
||||
function getPhpType(bool $allowNullable=true): ?string {
|
||||
# assumer mixed pour le moment
|
||||
#XXX à terme, lister les types de l'union
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,11 +1,28 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\types;
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nur\sery\cl;
|
||||
use nur\sery\wip\schema\Result;
|
||||
use nur\sery\wip\schema\Schema;
|
||||
use nulib\cl;
|
||||
use nulib\ValueException;
|
||||
use nulib\schema\_scalar\ScalarResult;
|
||||
use nulib\schema\_scalar\ScalarSchema;
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
|
||||
class tarray extends _tstring {
|
||||
const NAME = "array";
|
||||
|
||||
const SPLIT_PATTERN = '/\s+/';
|
||||
const FORMAT = " ";
|
||||
|
||||
public static function get_params_from_definition(?array $definition): ?array {
|
||||
$params = parent::get_params_from_definition($definition);
|
||||
$splitPattern = $definition["split_pattern"] ?? null;
|
||||
if ($splitPattern !== null) $params["split_pattern"] = $splitPattern;
|
||||
$format = $definition["format"] ?? null;
|
||||
if ($format !== null) $params["format"] = $format;
|
||||
return $params;
|
||||
}
|
||||
|
||||
class tarray extends _tsimple {
|
||||
static function ensure_array(&$array): void {
|
||||
if (!is_array($array)) $array = cl::with($array);
|
||||
}
|
||||
@ -14,14 +31,42 @@ class tarray extends _tsimple {
|
||||
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 getClass(): string {
|
||||
return "array";
|
||||
}
|
||||
|
||||
function verifix(&$value, Result &$result, Schema $schema): bool {
|
||||
function getNullValue() {
|
||||
return $this->nullable? null: [];
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = is_array($value);
|
||||
return $normalized || is_scalar($value);
|
||||
}
|
||||
|
||||
function parse(string $value) {
|
||||
$pattern = $this->params["split_pattern"] ?? static::SPLIT_PATTERN;
|
||||
return preg_split($pattern, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ScalarResult $result
|
||||
* @var ScalarSchema $schema
|
||||
*/
|
||||
function normalize(&$value, Result $result, Schema $schema): bool {
|
||||
if ($result->normalized) {
|
||||
} elseif (is_array($value)) {
|
||||
$result->setNormalized();
|
||||
} elseif (is_scalar($value)) {
|
||||
$value = cl::with($value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function format($value, $format=null): string {
|
||||
if ($value === null) return "";
|
||||
$format ??= $this->params["format"] ?? static::FORMAT;
|
||||
return implode($format, $value);
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,28 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\types;
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nur\sery\cl;
|
||||
use nur\sery\ValueException;
|
||||
use nur\sery\wip\schema\_scalar\ScalarResult;
|
||||
use nur\sery\wip\schema\_scalar\ScalarSchema;
|
||||
use nur\sery\wip\schema\input\Input;
|
||||
use nur\sery\wip\schema\Result;
|
||||
use nur\sery\wip\schema\Schema;
|
||||
use nulib\cl;
|
||||
use nulib\ValueException;
|
||||
use nur\prop;
|
||||
use nulib\schema\_scalar\ScalarResult;
|
||||
use nulib\schema\_scalar\ScalarSchema;
|
||||
use nulib\schema\input\Input;
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
|
||||
class tbool extends _tformatable {
|
||||
const NAME = "bool";
|
||||
|
||||
const ALIASES = ["boolean"];
|
||||
|
||||
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",
|
||||
];
|
||||
@ -53,41 +56,62 @@ class tbool extends _tsimple {
|
||||
if ($bool !== null) self::ensure_bool($bool);
|
||||
}
|
||||
|
||||
function isAvailable(Input $input, $destKey): bool {
|
||||
return $input->isAvailable($destKey);
|
||||
function getClass(): string {
|
||||
return "bool";
|
||||
}
|
||||
|
||||
function is2States(): bool {
|
||||
return !$this->nullable;
|
||||
}
|
||||
|
||||
function get2States(): array {
|
||||
return [false, true];
|
||||
}
|
||||
|
||||
function is3States(): bool {
|
||||
return $this->nullable;
|
||||
}
|
||||
|
||||
function get3States(): array {
|
||||
return [false, true, null];
|
||||
}
|
||||
|
||||
function getNullValue() {
|
||||
return $this->nullable? null: false;
|
||||
}
|
||||
|
||||
function isAvailable(Input $input, $valueKey): bool {
|
||||
return $input->isAvailable($valueKey);
|
||||
}
|
||||
|
||||
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;
|
||||
return is_scalar($value);
|
||||
}
|
||||
|
||||
function extract(string $value): string {
|
||||
return trim($value);
|
||||
}
|
||||
|
||||
function parse(string $value) {
|
||||
if (self::is_yes($value)) return true;
|
||||
elseif (self::is_no($value)) return false;
|
||||
throw new ValueException("une valeur booléenne est attendue");
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ScalarResult $result
|
||||
* @var ScalarSchema $schema
|
||||
*/
|
||||
function verifix(&$value, Result &$result, Schema $schema): bool {
|
||||
if (is_bool($value)) {
|
||||
function normalize(&$value, Result $result, Schema $schema): bool {
|
||||
if ($result->normalized) {
|
||||
} elseif (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);
|
||||
return true;
|
||||
}
|
||||
$result->setValid();
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
const OUINON_FORMAT = ["Oui", "Non", false];
|
||||
@ -105,10 +129,10 @@ class tbool extends _tsimple {
|
||||
"oz" => self::OZ_FORMAT,
|
||||
];
|
||||
|
||||
const DEFAULT_FORMAT = self::OUINON_FORMAT;
|
||||
const FORMAT = self::OUINON_FORMAT;
|
||||
|
||||
function format($value, $format=null): string {
|
||||
if ($format === null) $format = static::DEFAULT_FORMAT;
|
||||
$format ??= $this->params["format"] ?? static::FORMAT;
|
||||
if (is_string($format)) {
|
||||
$oformat = $format;
|
||||
$format = cl::get(self::FORMATS, strtolower($oformat));
|
||||
@ -120,4 +144,8 @@ class tbool extends _tsimple {
|
||||
}
|
||||
return $value? $format[0]: $format[1];
|
||||
}
|
||||
|
||||
function getGetterName(string $name): string {
|
||||
return prop::get_getter_name($name, !$this->nullable);
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\types;
|
||||
|
||||
use nur\sery\php\nur_func;
|
||||
use nur\sery\ValueException;
|
||||
use nur\sery\wip\schema\Result;
|
||||
use nur\sery\wip\schema\Schema;
|
||||
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 nur_func::check_func($value, stdClass::class);
|
||||
}
|
||||
|
||||
function verifix(&$value, Result &$result, Schema $schema): bool {
|
||||
}
|
||||
|
||||
function format($value, $format=null): string {
|
||||
}
|
||||
}
|
@ -1,26 +1,57 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\types;
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nur\sery\wip\schema\Result;
|
||||
use nur\sery\wip\schema\Schema;
|
||||
use nulib\php\content\c;
|
||||
use nulib\schema\_scalar\ScalarResult;
|
||||
use nulib\schema\_scalar\ScalarSchema;
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
|
||||
abstract class tcontent extends _tunion {
|
||||
const NAME = "content";
|
||||
|
||||
abstract class tcontent extends _tsimple {
|
||||
static function ensure_content(&$content): void {
|
||||
if (!is_string($content) && !is_array($content)) $content = strval($content);
|
||||
if ($content === null || $content === false) $content = [];
|
||||
elseif (!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 getClass(): string {
|
||||
return "string|array";
|
||||
}
|
||||
|
||||
function verifix(&$value, Result &$result, Schema $schema): bool {
|
||||
function getNullValue() {
|
||||
return $this->nullable? null: [];
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = is_string($value) || is_array($value);
|
||||
return $normalized || is_scalar($value);
|
||||
}
|
||||
|
||||
function parse(string $value) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ScalarResult $result
|
||||
* @var ScalarSchema $schema
|
||||
*/
|
||||
function normalize(&$value, Result $result, Schema $schema): bool {
|
||||
if ($result->normalized) {
|
||||
} elseif (is_string($value) || is_array($value)) {
|
||||
$result->setNormalized();
|
||||
} elseif (is_scalar($value)) {
|
||||
$value = strval($value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function format($value, $format=null): string {
|
||||
return c::to_string($value);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,17 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\types;
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nur\sery\wip\schema\_scalar\ScalarResult;
|
||||
use nur\sery\wip\schema\_scalar\ScalarSchema;
|
||||
use nur\sery\wip\schema\Result;
|
||||
use nur\sery\wip\schema\Schema;
|
||||
use nulib\ValueException;
|
||||
use nulib\schema\_scalar\ScalarResult;
|
||||
use nulib\schema\_scalar\ScalarSchema;
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
|
||||
class tfloat extends _tformatable {
|
||||
const NAME = "float";
|
||||
|
||||
const ALIASES = ["flt", "double", "dbl"];
|
||||
|
||||
class tfloat extends _tsimple {
|
||||
static function ensure_float(&$float): void {
|
||||
if (!is_float($float)) $float = floatval($float);
|
||||
}
|
||||
@ -15,36 +20,41 @@ class tfloat extends _tsimple {
|
||||
if ($float !== null) self::ensure_float($float);
|
||||
}
|
||||
|
||||
function getClass(): string {
|
||||
return "float";
|
||||
}
|
||||
|
||||
function getNullValue() {
|
||||
return $this->nullable? null: 0.0;
|
||||
}
|
||||
|
||||
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;
|
||||
return is_scalar($value);
|
||||
}
|
||||
|
||||
function extract(string $value): string {
|
||||
return trim($value);
|
||||
}
|
||||
|
||||
function parse(string $value) {
|
||||
$value = str_replace(",", ".", trim($value));
|
||||
if (is_numeric($value)) return floatval($value);
|
||||
throw new ValueException("une valeur numérique flottante est attendue");
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ScalarResult $result
|
||||
* @var ScalarSchema $schema
|
||||
*/
|
||||
function verifix(&$value, Result &$result, Schema $schema): bool {
|
||||
if (is_float($value)) {
|
||||
function normalize(&$value, Result $result, Schema $schema): bool {
|
||||
if ($result->normalized) {
|
||||
} elseif (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);
|
||||
return true;
|
||||
}
|
||||
$result->setValid();
|
||||
return true;
|
||||
}
|
||||
|
||||
function format($value, $format=null): string {
|
||||
if ($format !== null) return sprintf($format, $value);
|
||||
else return strval($value);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
63
src/schema/types/tfunc.php
Normal file
63
src/schema/types/tfunc.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use Exception;
|
||||
use nulib\php\func;
|
||||
use nulib\ValueException;
|
||||
use nulib\schema\_scalar\ScalarResult;
|
||||
use nulib\schema\_scalar\ScalarSchema;
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
|
||||
class tfunc extends _tsimple {
|
||||
const NAME = "func";
|
||||
|
||||
const ALIASES = ["function", "callable"];
|
||||
|
||||
static function ensure_func(&$func): void {
|
||||
$func = func::ensure($func);
|
||||
}
|
||||
|
||||
static function ensure_nfunc(&$func): void {
|
||||
if ($func !== null) self::ensure_func($func);
|
||||
}
|
||||
|
||||
function getClass(): string {
|
||||
return func::class;
|
||||
}
|
||||
|
||||
function getNullValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = $value instanceof func;
|
||||
return func::check($value);
|
||||
}
|
||||
|
||||
function parse(string $value) {
|
||||
try {
|
||||
return func::ensure($value);
|
||||
} catch (Exception $e) {
|
||||
throw new ValueException(null, null, 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ScalarResult $result
|
||||
* @var ScalarSchema $schema
|
||||
*/
|
||||
function normalize(&$value, Result $result, Schema $schema): bool {
|
||||
if ($result->normalized) {
|
||||
} elseif ($value instanceof func) {
|
||||
$result->setNormalized();
|
||||
} elseif (is_callable($value)) {
|
||||
$value = func::with($value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function format($value, $format=null): string {
|
||||
}
|
||||
}
|
56
src/schema/types/tgeneric.php
Normal file
56
src/schema/types/tgeneric.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nulib\ValueException;
|
||||
use nulib\schema\_scalar\ScalarResult;
|
||||
use nulib\schema\_scalar\ScalarSchema;
|
||||
use nulib\schema\input\Input;
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
|
||||
class tgeneric extends _tsimple {
|
||||
function __construct(string $class, bool $nullable, ?array $params=null) {
|
||||
$this->class = $class;
|
||||
parent::__construct($nullable, $params);
|
||||
}
|
||||
|
||||
protected string $class;
|
||||
|
||||
function getClass(): string {
|
||||
return $this->class;
|
||||
}
|
||||
|
||||
function getNullValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
function isAvailable(Input $input, $valueKey): bool {
|
||||
return $input->isAvailable($valueKey);
|
||||
}
|
||||
|
||||
public function isNull($value): bool {
|
||||
return $value === null;
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = $value instanceof $this->class;
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
function parse(string $value) {
|
||||
throw ValueException::invalid_type($value, $this->class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ScalarResult $result
|
||||
* @var ScalarSchema $schema
|
||||
*/
|
||||
function normalize(&$value, Result $result, Schema $schema): bool {
|
||||
if (!$result->normalized) $result->setNormalized();
|
||||
return false;
|
||||
}
|
||||
|
||||
function format($value, $format=null): string {
|
||||
return strval($value);
|
||||
}
|
||||
}
|
@ -1,12 +1,17 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\types;
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nur\sery\wip\schema\_scalar\ScalarResult;
|
||||
use nur\sery\wip\schema\_scalar\ScalarSchema;
|
||||
use nur\sery\wip\schema\Result;
|
||||
use nur\sery\wip\schema\Schema;
|
||||
use nulib\ValueException;
|
||||
use nulib\schema\_scalar\ScalarResult;
|
||||
use nulib\schema\_scalar\ScalarSchema;
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
|
||||
class tint extends _tformatable {
|
||||
const NAME = "int";
|
||||
|
||||
const ALIASES = ["integer"];
|
||||
|
||||
class tint extends _tsimple {
|
||||
static function ensure_int(&$int): void {
|
||||
if (!is_int($int)) $int = intval($int);
|
||||
}
|
||||
@ -15,38 +20,43 @@ class tint extends _tsimple {
|
||||
if ($int !== null) self::ensure_int($int);
|
||||
}
|
||||
|
||||
const INT_PATTERN = '/^[-+]?[0-9]+(?:\.[0-9]*)?$/';
|
||||
//const INT_PATTERN = '/^[-+]?[0-9]+(?:\.[0-9]*)?$/';
|
||||
|
||||
function getClass(): string {
|
||||
return "int";
|
||||
}
|
||||
|
||||
function getNullValue() {
|
||||
return $this->nullable? null: 0;
|
||||
}
|
||||
|
||||
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;
|
||||
return is_scalar($value);
|
||||
}
|
||||
|
||||
function extract(string $value): string {
|
||||
return trim($value);
|
||||
}
|
||||
|
||||
function parse(string $value) {
|
||||
$value = str_replace(",", ".", trim($value));
|
||||
if (is_numeric($value)) return intval($value);
|
||||
throw new ValueException("une valeur numérique entière est attendue");
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ScalarResult $result
|
||||
* @var ScalarSchema $schema
|
||||
*/
|
||||
function verifix(&$value, Result &$result, Schema $schema): bool {
|
||||
if (is_int($value)) {
|
||||
function normalize(&$value, Result $result, Schema $schema): bool {
|
||||
if ($result->normalized) {
|
||||
} elseif (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);
|
||||
return true;
|
||||
}
|
||||
$result->setValid();
|
||||
return true;
|
||||
}
|
||||
|
||||
function format($value, $format=null): string {
|
||||
if ($format !== null) return sprintf($format, $value);
|
||||
else return strval($value);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,57 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\types;
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nur\sery\wip\schema\Result;
|
||||
use nur\sery\wip\schema\Schema;
|
||||
use nulib\schema\_scalar\ScalarResult;
|
||||
use nulib\schema\_scalar\ScalarSchema;
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
|
||||
class tkey extends _tunion {
|
||||
const NAME = "key";
|
||||
|
||||
class tkey extends _tsimple {
|
||||
static function ensure_key(&$key): void {
|
||||
if (!is_string($key) && !is_int($key)) $key = strval($key);
|
||||
if ($key === null) $key = "";
|
||||
elseif ($key === false) $key = 0;
|
||||
elseif (!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 getClass(): string {
|
||||
return "string|int";
|
||||
}
|
||||
|
||||
function verifix(&$value, Result &$result, Schema $schema): bool {
|
||||
function getNullValue() {
|
||||
return $this->nullable? null: "";
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = is_string($value) || is_int($value);
|
||||
return $normalized || is_scalar($value);
|
||||
}
|
||||
|
||||
function parse(string $value) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ScalarResult $result
|
||||
* @var ScalarSchema $schema
|
||||
*/
|
||||
function normalize(&$value, Result $result, Schema $schema): bool {
|
||||
if ($result->normalized) {
|
||||
} elseif (is_string($value) || is_int($value)) {
|
||||
$result->setNormalized();
|
||||
} elseif (is_scalar($value)) {
|
||||
$value = strval($value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function format($value, $format=null): string {
|
||||
return strval($value);
|
||||
}
|
||||
}
|
||||
|
50
src/schema/types/tmixed.php
Normal file
50
src/schema/types/tmixed.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nulib\schema\_scalar\ScalarResult;
|
||||
use nulib\schema\_scalar\ScalarSchema;
|
||||
use nulib\schema\input\Input;
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
|
||||
class tmixed extends _tsimple {
|
||||
const NAME = "mixed";
|
||||
|
||||
function getClass(): string {
|
||||
return "mixed";
|
||||
}
|
||||
|
||||
function getNullValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
function isAvailable(Input $input, $valueKey): bool {
|
||||
return $input->isAvailable($valueKey);
|
||||
}
|
||||
|
||||
public function isNull($value): bool {
|
||||
return $value === null;
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
function parse(string $value) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ScalarResult $result
|
||||
* @var ScalarSchema $schema
|
||||
*/
|
||||
function normalize(&$value, Result $result, Schema $schema): bool {
|
||||
if (!$result->normalized) $result->setNormalized();
|
||||
return false;
|
||||
}
|
||||
|
||||
function format($value, $format=null): string {
|
||||
return strval($value);
|
||||
}
|
||||
}
|
@ -1,17 +1,22 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\types;
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nur\sery\wip\schema\Result;
|
||||
use nur\sery\wip\schema\Schema;
|
||||
use nulib\schema\_scalar\ScalarResult;
|
||||
use nulib\schema\_scalar\ScalarSchema;
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
|
||||
class tpkey extends _tunion {
|
||||
const NAME = "pkey";
|
||||
|
||||
class tpkey extends _tsimple {
|
||||
static function ensure_pkey(&$pkey): void {
|
||||
if (!is_string($pkey) && !is_int($pkey) && !is_array($pkey)) $pkey = strval($pkey);
|
||||
if ($pkey === null) $pkey = "";
|
||||
elseif ($pkey === false) $pkey = 0;
|
||||
elseif (!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);
|
||||
}; unset($key);
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,14 +24,40 @@ class tpkey extends _tsimple {
|
||||
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 getClass(): string {
|
||||
return "string|int|array";
|
||||
}
|
||||
|
||||
function verifix(&$value, Result &$result, Schema $schema): bool {
|
||||
function getNullValue() {
|
||||
return $this->nullable? null: [];
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = is_string($value) || is_int($value) || is_array($value);
|
||||
return $normalized || is_scalar($value);
|
||||
}
|
||||
|
||||
function parse(string $value) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ScalarResult $result
|
||||
* @var ScalarSchema $schema
|
||||
*/
|
||||
function normalize(&$value, Result $result, Schema $schema): bool {
|
||||
if ($result->normalized) {
|
||||
} elseif (is_string($value) || is_int($value) || is_array($value)) {
|
||||
$result->setNormalized();
|
||||
} elseif (is_scalar($value)) {
|
||||
$value = strval($value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function format($value, $format=null): string {
|
||||
if (is_array($value)) return implode(".", $value);
|
||||
else return strval($value);
|
||||
}
|
||||
}
|
||||
|
16
src/schema/types/traw.php
Normal file
16
src/schema/types/traw.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nulib\schema\input\Input;
|
||||
|
||||
class traw extends tmixed {
|
||||
const NAME = "raw";
|
||||
|
||||
function isAvailable(Input $input, $valueKey): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isNull($value): bool {
|
||||
return false;
|
||||
}
|
||||
}
|
65
src/schema/types/trawstring.php
Normal file
65
src/schema/types/trawstring.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nulib\str;
|
||||
use nulib\schema\_scalar\ScalarResult;
|
||||
use nulib\schema\_scalar\ScalarSchema;
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
|
||||
class trawstring extends _tstring {
|
||||
const NAME = "rawstring";
|
||||
|
||||
static function ensure_string(&$string): void {
|
||||
if (!is_string($string)) $string = strval($string);
|
||||
if (static::TRIM) $string = trim($string);
|
||||
if (static::NORM_NL) $string = str::norm_nl($string);
|
||||
}
|
||||
|
||||
static function ensure_nstring(&$string): void {
|
||||
if ($string !== null) self::ensure_string($string);
|
||||
}
|
||||
|
||||
function getClass(): string {
|
||||
return "string";
|
||||
}
|
||||
|
||||
function getNullValue() {
|
||||
return $this->nullable? null: "";
|
||||
}
|
||||
|
||||
function isNull($value): bool {
|
||||
return $value === null;
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
if (is_string($value)) {
|
||||
$normalized = true;
|
||||
return true;
|
||||
}
|
||||
return is_scalar($value);
|
||||
}
|
||||
|
||||
function parse(string $value) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ScalarResult $result
|
||||
* @var ScalarSchema $schema
|
||||
*/
|
||||
function normalize(&$value, Result $result, Schema $schema): bool {
|
||||
if ($result->normalized) {
|
||||
} elseif (is_string($value)) {
|
||||
$result->setNormalized();
|
||||
} elseif (is_scalar($value)) {
|
||||
$value = strval($value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function format($value, $format=null): string {
|
||||
return strval($value);
|
||||
}
|
||||
}
|
@ -1,48 +1,8 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\types;
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nur\sery\wip\schema\_scalar\ScalarResult;
|
||||
use nur\sery\wip\schema\_scalar\ScalarSchema;
|
||||
use nur\sery\wip\schema\Result;
|
||||
use nur\sery\wip\schema\Schema;
|
||||
class tstring extends trawstring {
|
||||
const NAME = "string";
|
||||
|
||||
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);
|
||||
}
|
||||
const TRIM = true;
|
||||
}
|
||||
|
9
src/schema/types/ttext.php
Normal file
9
src/schema/types/ttext.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
class ttext extends trawstring {
|
||||
const NAME = "text";
|
||||
|
||||
const TRIM = true;
|
||||
const NORM_NL = true;
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\web\content;
|
||||
namespace nulib\web\content;
|
||||
|
||||
use nur\sery\A;
|
||||
use nur\sery\php\content\c;
|
||||
use nur\sery\php\content\IContent;
|
||||
|
||||
use nulib\A;
|
||||
use nulib\php\content\c;
|
||||
use nulib\php\content\IContent;
|
||||
|
||||
class Tag implements IContent {
|
||||
function __construct(string $tag, $content=null) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\web\content;
|
||||
namespace nulib\web\content;
|
||||
|
||||
/**
|
||||
* Class v: classe outil pour gérer du contenu pour le web
|
||||
|
@ -1,5 +0,0 @@
|
||||
<?php
|
||||
namespace nulib;
|
||||
|
||||
class app extends \nur\sery\app {
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\app\cli;
|
||||
|
||||
abstract class Application extends \nur\sery\app\cli\Application {
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace nur\sery\app;
|
||||
|
||||
use nulib\tests\TestCase;
|
||||
use nur\sery\app\args;
|
||||
|
||||
class argsTest extends TestCase {
|
||||
function testFrom_array() {
|
||||
self::assertSame([], args::from_array(null));
|
||||
self::assertSame([], args::from_array([]));
|
||||
self::assertSame([], args::from_array([false]));
|
||||
self::assertSame(["x"], args::from_array(["x", false]));
|
||||
|
||||
self::assertSame(["--opt"], args::from_array(["--opt"]));
|
||||
self::assertSame(["--opt", "value"], args::from_array(["--opt", "value"]));
|
||||
|
||||
self::assertSame([], args::from_array(["opt" => false]));
|
||||
self::assertSame(["--opt"], args::from_array(["opt" => true]));
|
||||
self::assertSame(["--opt", "value"], args::from_array(["opt" => "value"]));
|
||||
self::assertSame(["--opt", "42"], args::from_array(["opt" => 42]));
|
||||
self::assertSame(["--opt", "1", "2", "3", "--"], args::from_array(["opt" => [1, 2, 3]]));
|
||||
|
||||
self::assertSame(["x", "1", "2", "3", "y"], args::from_array(["x", [1, 2, 3], "y"]));
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
<?php
|
||||
namespace nur\sery {
|
||||
namespace nulib {
|
||||
use nulib\tests\TestCase;
|
||||
use nur\sery\impl\config;
|
||||
use nur\sery\impl\myapp;
|
||||
use nur\sery\impl\MyApplication1;
|
||||
use nur\sery\impl\MyApplication2;
|
||||
use nulib\impl\config;
|
||||
use nulib\impl\myapp;
|
||||
use nulib\impl\MyApplication1;
|
||||
use nulib\impl\MyApplication2;
|
||||
|
||||
class appTest extends TestCase {
|
||||
function testWith() {
|
||||
@ -97,11 +97,11 @@ namespace nur\sery {
|
||||
}
|
||||
}
|
||||
|
||||
namespace nur\sery\impl {
|
||||
namespace nulib\impl {
|
||||
|
||||
use nur\sery\app\cli\Application;
|
||||
use nur\sery\os\path;
|
||||
use nur\sery\app;
|
||||
use nulib\app\cli\Application;
|
||||
use nulib\os\path;
|
||||
use nulib\app;
|
||||
|
||||
class config {
|
||||
const PROJDIR = __DIR__.'/..';
|
||||
|
1
tests/db/sqlite/.gitignore
vendored
1
tests/db/sqlite/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/capacitor.db*
|
@ -1,344 +0,0 @@
|
||||
<?php
|
||||
namespace nur\sery\db\sqlite;
|
||||
|
||||
use nulib\tests\TestCase;
|
||||
use nur\sery\cl;
|
||||
use nur\sery\db\Capacitor;
|
||||
use nur\sery\db\CapacitorChannel;
|
||||
|
||||
class SqliteStorageTest extends TestCase {
|
||||
function _testChargeStrings(SqliteStorage $storage, ?string $channel) {
|
||||
$storage->reset($channel);
|
||||
$storage->charge($channel, "first");
|
||||
$storage->charge($channel, "second");
|
||||
$storage->charge($channel, "third");
|
||||
$items = cl::all($storage->discharge($channel, false));
|
||||
self::assertSame(["first", "second", "third"], $items);
|
||||
}
|
||||
|
||||
function _testChargeArrays(SqliteStorage $storage, ?string $channel) {
|
||||
$storage->reset($channel);
|
||||
$storage->charge($channel, ["id" => 10, "name" => "first"]);
|
||||
$storage->charge($channel, ["name" => "second", "id" => 20]);
|
||||
$storage->charge($channel, ["name" => "third", "id" => "30"]);
|
||||
}
|
||||
|
||||
function testChargeStrings() {
|
||||
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||
$this->_testChargeStrings($storage, null);
|
||||
$storage->close();
|
||||
}
|
||||
|
||||
function testChargeArrays() {
|
||||
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||
$storage->addChannel(new class extends CapacitorChannel {
|
||||
const NAME = "arrays";
|
||||
const COLUMN_DEFINITIONS = ["id" => "integer"];
|
||||
|
||||
function getItemValues($item): ?array {
|
||||
return ["id" => $item["id"] ?? null];
|
||||
}
|
||||
});
|
||||
|
||||
$this->_testChargeStrings($storage, "strings");
|
||||
$this->_testChargeArrays($storage, "arrays");
|
||||
$storage->close();
|
||||
}
|
||||
|
||||
function testEach() {
|
||||
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||
$capacitor = new Capacitor($storage, new class extends CapacitorChannel {
|
||||
const NAME = "each";
|
||||
const COLUMN_DEFINITIONS = [
|
||||
"age" => "integer",
|
||||
"done" => "integer default 0",
|
||||
];
|
||||
|
||||
function getItemValues($item): ?array {
|
||||
return [
|
||||
"age" => $item["age"],
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
$capacitor->reset();
|
||||
$capacitor->charge(["name" => "first", "age" => 5]);
|
||||
$capacitor->charge(["name" => "second", "age" => 10]);
|
||||
$capacitor->charge(["name" => "third", "age" => 15]);
|
||||
$capacitor->charge(["name" => "fourth", "age" => 20]);
|
||||
|
||||
$setDone = function ($item, $row, $suffix=null) {
|
||||
$updates = ["done" => 1];
|
||||
if ($suffix !== null) {
|
||||
$item["name"] .= $suffix;
|
||||
$updates["item"] = $item;
|
||||
}
|
||||
return $updates;
|
||||
};
|
||||
$capacitor->each(["age" => [">", 10]], $setDone, ["++"]);
|
||||
$capacitor->each(["done" => 0], $setDone, null);
|
||||
|
||||
Txx(cl::all($capacitor->discharge(false)));
|
||||
$capacitor->close();
|
||||
self::assertTrue(true);
|
||||
}
|
||||
|
||||
function testPrimayKey() {
|
||||
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||
$capacitor = new Capacitor($storage, new class extends CapacitorChannel {
|
||||
const NAME = "pk";
|
||||
const COLUMN_DEFINITIONS = [
|
||||
"id_" => "varchar primary key",
|
||||
"done" => "integer default 0",
|
||||
];
|
||||
|
||||
function getItemValues($item): ?array {
|
||||
return [
|
||||
"id_" => $item["numero"],
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
$capacitor->charge(["numero" => "a", "name" => "first", "age" => 5]);
|
||||
$capacitor->charge(["numero" => "b", "name" => "second", "age" => 10]);
|
||||
$capacitor->charge(["numero" => "c", "name" => "third", "age" => 15]);
|
||||
$capacitor->charge(["numero" => "d", "name" => "fourth", "age" => 20]);
|
||||
sleep(2);
|
||||
$capacitor->charge(["numero" => "b", "name" => "second", "age" => 100]);
|
||||
$capacitor->charge(["numero" => "d", "name" => "fourth", "age" => 200]);
|
||||
|
||||
$capacitor->close();
|
||||
self::assertTrue(true);
|
||||
}
|
||||
|
||||
function testSum() {
|
||||
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||
$capacitor = new Capacitor($storage, new class extends CapacitorChannel {
|
||||
const NAME = "sum";
|
||||
const COLUMN_DEFINITIONS = [
|
||||
"a__" => "varchar",
|
||||
"b__" => "varchar",
|
||||
"b__sum_" => self::SUM_DEFINITION,
|
||||
];
|
||||
|
||||
function getItemValues($item): ?array {
|
||||
return [
|
||||
"a" => $item["a"],
|
||||
"b" => $item["b"],
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
$capacitor->reset();
|
||||
$capacitor->charge(["a" => null, "b" => null]);
|
||||
$capacitor->charge(["a" => "first", "b" => "second"]);
|
||||
|
||||
Txx("=== all");
|
||||
/** @var Sqlite $sqlite */
|
||||
$sqlite = $capacitor->getStorage()->db();
|
||||
Txx(cl::all($sqlite->all([
|
||||
"select",
|
||||
"from" => $capacitor->getChannel()->getTableName(),
|
||||
])));
|
||||
Txx("=== each");
|
||||
$capacitor->each(null, function ($item, $values) {
|
||||
Txx($values);
|
||||
});
|
||||
|
||||
$capacitor->close();
|
||||
self::assertTrue(true);
|
||||
}
|
||||
|
||||
function testEachValues() {
|
||||
# tester que values contient bien toutes les valeurs de la ligne
|
||||
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||
$capacitor = new Capacitor($storage, new class extends CapacitorChannel {
|
||||
const NAME = "each_values";
|
||||
const COLUMN_DEFINITIONS = [
|
||||
"name" => "varchar primary key",
|
||||
"age" => "integer",
|
||||
"done" => "integer default 0",
|
||||
"notes" => "text",
|
||||
];
|
||||
|
||||
function getItemValues($item): ?array {
|
||||
return [
|
||||
"name" => $item["name"],
|
||||
"age" => $item["age"],
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
$capacitor->reset();
|
||||
$capacitor->charge(["name" => "first", "age" => 5], function($item, ?array $values, ?array $pvalues) {
|
||||
self::assertSame("first", $item["name"]);
|
||||
self::assertSame(5, $item["age"]);
|
||||
self::assertnotnull($values);
|
||||
self::assertSame(["name", "age", "item", "item__sum_", "created_", "modified_"], array_keys($values));
|
||||
self::assertSame([
|
||||
"name" => "first",
|
||||
"age" => 5,
|
||||
"item" => $item,
|
||||
], cl::select($values, ["name", "age", "item"]));
|
||||
self::assertNull($pvalues);
|
||||
});
|
||||
$capacitor->charge(["name" => "first", "age" => 10], function($item, ?array $values, ?array $pvalues) {
|
||||
self::assertSame("first", $item["name"]);
|
||||
self::assertSame(10, $item["age"]);
|
||||
self::assertnotnull($values);
|
||||
self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($values));
|
||||
self::assertSame([
|
||||
"name" => "first",
|
||||
"age" => 10,
|
||||
"done" => 0,
|
||||
"notes" => null,
|
||||
"item" => $item,
|
||||
], cl::select($values, ["name", "age", "done", "notes", "item"]));
|
||||
self::assertNotNull($pvalues);
|
||||
self::assertSame([
|
||||
"name" => "first",
|
||||
"age" => 5,
|
||||
"done" => 0,
|
||||
"notes" => null,
|
||||
"item" => ["name" => "first", "age" => 5],
|
||||
], cl::select($pvalues, ["name", "age", "done", "notes", "item"]));
|
||||
});
|
||||
|
||||
$capacitor->each(null, function($item, ?array $values) {
|
||||
self::assertSame("first", $item["name"]);
|
||||
self::assertSame(10, $item["age"]);
|
||||
self::assertnotnull($values);
|
||||
self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($values));
|
||||
self::assertSame([
|
||||
"name" => "first",
|
||||
"age" => 10,
|
||||
"done" => 0,
|
||||
"notes" => null,
|
||||
"item" => $item,
|
||||
], cl::select($values, ["name", "age", "done", "notes", "item"]));
|
||||
return [
|
||||
"done" => 1,
|
||||
"notes" => "modified",
|
||||
];
|
||||
});
|
||||
$capacitor->charge(["name" => "first", "age" => 10], function($item, ?array $values, ?array $pvalues) {
|
||||
self::assertSame("first", $item["name"]);
|
||||
self::assertSame(10, $item["age"]);
|
||||
self::assertnotnull($values);
|
||||
self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($values));
|
||||
self::assertSame([
|
||||
"name" => "first",
|
||||
"age" => 10,
|
||||
"done" => 1,
|
||||
"notes" => "modified",
|
||||
"item" => $item,
|
||||
], cl::select($values, ["name", "age", "done", "notes", "item"]));
|
||||
self::assertNotNull($pvalues);
|
||||
self::assertSame([
|
||||
"name" => "first",
|
||||
"age" => 10,
|
||||
"done" => 1,
|
||||
"notes" => "modified",
|
||||
"item" => $item,
|
||||
], cl::select($pvalues, ["name", "age", "done", "notes", "item"]));
|
||||
});
|
||||
|
||||
$capacitor->charge(["name" => "first", "age" => 20], function($item, ?array $values, ?array $pvalues) {
|
||||
self::assertSame("first", $item["name"]);
|
||||
self::assertSame(20, $item["age"]);
|
||||
self::assertnotnull($values);
|
||||
self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($values));
|
||||
self::assertSame([
|
||||
"name" => "first",
|
||||
"age" => 20,
|
||||
"done" => 1,
|
||||
"notes" => "modified",
|
||||
"item" => $item,
|
||||
], cl::select($values, ["name", "age", "done", "notes", "item"]));
|
||||
self::assertNotNull($pvalues);
|
||||
self::assertSame([
|
||||
"name" => "first",
|
||||
"age" => 10,
|
||||
"done" => 1,
|
||||
"notes" => "modified",
|
||||
"item" => ["name" => "first", "age" => 10],
|
||||
], cl::select($pvalues, ["name", "age", "done", "notes", "item"]));
|
||||
});
|
||||
}
|
||||
|
||||
function testSetItemNull() {
|
||||
# tester le forçage de $îtem à null pour économiser la place
|
||||
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||
$capacitor = new Capacitor($storage, new class extends CapacitorChannel {
|
||||
const NAME = "set_item_null";
|
||||
const COLUMN_DEFINITIONS = [
|
||||
"name" => "varchar primary key",
|
||||
"age" => "integer",
|
||||
"done" => "integer default 0",
|
||||
"notes" => "text",
|
||||
];
|
||||
|
||||
function getItemValues($item): ?array {
|
||||
return [
|
||||
"name" => $item["name"],
|
||||
"age" => $item["age"],
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
$capacitor->reset();
|
||||
$nbModified = $capacitor->charge(["name" => "first", "age" => 5], function ($item, ?array $values, ?array $pvalues) {
|
||||
self::assertSame([
|
||||
"name" => "first", "age" => 5,
|
||||
"item" => $item,
|
||||
], cl::select($values, ["name", "age", "item"]));
|
||||
return ["item" => null];
|
||||
});
|
||||
self::assertSame(1, $nbModified);
|
||||
sleep(1);
|
||||
# nb: on met des sleep() pour que la date de modification soit systématiquement différente
|
||||
|
||||
$nbModified = $capacitor->charge(["name" => "first", "age" => 10], function ($item, ?array $values, ?array $pvalues) {
|
||||
self::assertSame([
|
||||
"name" => "first", "age" => 10,
|
||||
"item" => $item, "item__sum_" => "9181336dfca20c86313d6065d89aa2ad5070b0fc",
|
||||
], cl::select($values, ["name", "age", "item", "item__sum_"]));
|
||||
self::assertSame([
|
||||
"name" => "first", "age" => 5,
|
||||
"item" => null, "item__sum_" => null,
|
||||
], cl::select($pvalues, ["name", "age", "item", "item__sum_"]));
|
||||
return ["item" => null];
|
||||
});
|
||||
self::assertSame(1, $nbModified);
|
||||
sleep(1);
|
||||
|
||||
# pas de modification ici
|
||||
$nbModified = $capacitor->charge(["name" => "first", "age" => 10], function ($item, ?array $values, ?array $pvalues) {
|
||||
self::assertSame([
|
||||
"name" => "first", "age" => 10,
|
||||
"item" => $item, "item__sum_" => "9181336dfca20c86313d6065d89aa2ad5070b0fc",
|
||||
], cl::select($values, ["name", "age", "item", "item__sum_"]));
|
||||
self::assertSame([
|
||||
"name" => "first", "age" => 10,
|
||||
"item" => null, "item__sum_" => null,
|
||||
], cl::select($pvalues, ["name", "age", "item", "item__sum_"]));
|
||||
return ["item" => null];
|
||||
});
|
||||
self::assertSame(0, $nbModified);
|
||||
sleep(1);
|
||||
|
||||
$nbModified = $capacitor->charge(["name" => "first", "age" => 20], function ($item, ?array $values, ?array $pvalues) {
|
||||
self::assertSame([
|
||||
"name" => "first", "age" => 20,
|
||||
"item" => $item, "item__sum_" => "001b91982b4e0883b75428c0eb28573a5dc5f7a5",
|
||||
], cl::select($values, ["name", "age", "item", "item__sum_"]));
|
||||
self::assertSame([
|
||||
"name" => "first", "age" => 10,
|
||||
"item" => null, "item__sum_" => null,
|
||||
], cl::select($pvalues, ["name", "age", "item", "item__sum_"]));
|
||||
return ["item" => null];
|
||||
});
|
||||
self::assertSame(1, $nbModified);
|
||||
sleep(1);
|
||||
}
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
<?php
|
||||
namespace nur\sery\db\sqlite;
|
||||
|
||||
use Exception;
|
||||
use nulib\tests\TestCase;
|
||||
|
||||
class SqliteTest extends TestCase {
|
||||
const CREATE_PERSON = "create table person(nom varchar, prenom varchar, age integer)";
|
||||
const INSERT_JEPHTE = "insert into person(nom, prenom, age) values ('clain', 'jephte', 50)";
|
||||
const INSERT_JEAN = "insert into person(nom, prenom, age) values ('payet', 'jean', 32)";
|
||||
|
||||
function testMigration() {
|
||||
$sqlite = new Sqlite(":memory:", [
|
||||
"migrate" => [
|
||||
self::CREATE_PERSON,
|
||||
self::INSERT_JEPHTE,
|
||||
],
|
||||
]);
|
||||
self::assertSame("clain", $sqlite->get("select nom, age from person"));
|
||||
self::assertSame([
|
||||
"nom" => "clain",
|
||||
"age" => 50,
|
||||
], $sqlite->get("select nom, age from person", null, true));
|
||||
|
||||
$sqlite->exec(self::INSERT_JEAN);
|
||||
self::assertSame("payet", $sqlite->get("select nom, age from person where nom = 'payet'"));
|
||||
self::assertSame([
|
||||
"nom" => "payet",
|
||||
"age" => 32,
|
||||
], $sqlite->get("select nom, age from person where nom = 'payet'", null, true));
|
||||
|
||||
self::assertSame([
|
||||
["key" => "0", "value" => self::CREATE_PERSON, "done" => 1],
|
||||
["key" => "1", "value" => self::INSERT_JEPHTE, "done" => 1],
|
||||
], iterator_to_array($sqlite->all("select key, value, done from _migration")));
|
||||
}
|
||||
|
||||
function testException() {
|
||||
$sqlite = new Sqlite(":memory:");
|
||||
self::assertException(Exception::class, [$sqlite, "exec"], "prout");
|
||||
self::assertException(SqliteException::class, [$sqlite, "exec"], ["prout"]);
|
||||
}
|
||||
|
||||
protected function assertInserted(Sqlite $sqlite, array $row, array $query): void {
|
||||
$sqlite->exec($query);
|
||||
self::assertSame($row, $sqlite->one("select * from mapping where i = :i", [
|
||||
"i" => $query["values"]["i"],
|
||||
]));
|
||||
}
|
||||
function testInsert() {
|
||||
$sqlite = new Sqlite(":memory:", [
|
||||
"migrate" => "create table mapping (i integer, s varchar)",
|
||||
]);
|
||||
$sqlite->exec(["insert into mapping", "values" => ["i" => 1, "s" => "un"]]);
|
||||
$sqlite->exec(["insert mapping", "values" => ["i" => 2, "s" => "deux"]]);
|
||||
$sqlite->exec(["insert into", "into" => "mapping", "values" => ["i" => 3, "s" => "trois"]]);
|
||||
$sqlite->exec(["insert", "into" => "mapping", "values" => ["i" => 4, "s" => "quatre"]]);
|
||||
$sqlite->exec(["insert into mapping(i)", "values" => ["i" => 5, "s" => "cinq"]]);
|
||||
$sqlite->exec(["insert into (i)", "into" => "mapping", "values" => ["i" => 6, "s" => "six"]]);
|
||||
$sqlite->exec(["insert into mapping(i) values ()", "values" => ["i" => 7, "s" => "sept"]]);
|
||||
$sqlite->exec(["insert into mapping(i) values (8)", "values" => ["i" => 42, "s" => "whatever"]]);
|
||||
$sqlite->exec(["insert into mapping(i, s) values (9, 'neuf')", "values" => ["i" => 43, "s" => "garbage"]]);
|
||||
$sqlite->exec(["insert into mapping", "cols" => ["i"], "values" => ["i" => 10, "s" => "dix"]]);
|
||||
|
||||
self::assertSame([
|
||||
["i" => 1, "s" => "un"],
|
||||
["i" => 2, "s" => "deux"],
|
||||
["i" => 3, "s" => "trois"],
|
||||
["i" => 4, "s" => "quatre"],
|
||||
["i" => 5, "s" => null/*"cinq"*/],
|
||||
["i" => 6, "s" => null/*"six"*/],
|
||||
["i" => 7, "s" => null/*"sept"*/],
|
||||
["i" => 8, "s" => null/*"huit"*/],
|
||||
["i" => 9, "s" => "neuf"],
|
||||
["i" => 10, "s" => null/*"dix"*/],
|
||||
], iterator_to_array($sqlite->all("select * from mapping")));
|
||||
}
|
||||
|
||||
function testSelect() {
|
||||
$sqlite = new Sqlite(":memory:", [
|
||||
"migrate" => "create table user (name varchar, amount integer)",
|
||||
]);
|
||||
$sqlite->exec(["insert into user", "values" => ["name" => "jclain1", "amount" => 1]]);
|
||||
$sqlite->exec(["insert into user", "values" => ["name" => "jclain2", "amount" => 2]]);
|
||||
$sqlite->exec(["insert into user", "values" => ["name" => "jclain5", "amount" => 5]]);
|
||||
$sqlite->exec(["insert into user", "values" => ["name" => "fclain7", "amount" => 7]]);
|
||||
$sqlite->exec(["insert into user", "values" => ["name" => "fclain9", "amount" => 9]]);
|
||||
$sqlite->exec(["insert into user", "values" => ["name" => "fclain10", "amount" => 10]]);
|
||||
self::assertSame([
|
||||
"name" => "jclain1",
|
||||
"amount" => 1,
|
||||
], $sqlite->one("select * from user where name = 'jclain1'"));
|
||||
self::assertSame([
|
||||
"name" => "jclain1",
|
||||
"amount" => 1,
|
||||
], $sqlite->one(["select * from user where name = 'jclain1'"]));
|
||||
self::assertSame([
|
||||
"name" => "jclain1",
|
||||
"amount" => 1,
|
||||
], $sqlite->one(["select from user where name = 'jclain1'"]));
|
||||
self::assertSame([
|
||||
"name" => "jclain1",
|
||||
"amount" => 1,
|
||||
], $sqlite->one(["select from user where", "where" => ["name = 'jclain1'"]]));
|
||||
self::assertSame([
|
||||
"name" => "jclain1",
|
||||
"amount" => 1,
|
||||
], $sqlite->one(["select from user", "where" => ["name = 'jclain1'"]]));
|
||||
self::assertSame([
|
||||
"name" => "jclain1",
|
||||
"amount" => 1,
|
||||
], $sqlite->one(["select", "from" => "user", "where" => ["name = 'jclain1'"]]));
|
||||
self::assertSame([
|
||||
"name" => "jclain1",
|
||||
"amount" => 1,
|
||||
], $sqlite->one(["select", "from" => "user", "where" => ["name" => "jclain1"]]));
|
||||
self::assertSame([
|
||||
"name" => "jclain1",
|
||||
], $sqlite->one(["select name", "from" => "user", "where" => ["name" => "jclain1"]]));
|
||||
self::assertSame([
|
||||
"name" => "jclain1",
|
||||
], $sqlite->one(["select", "cols" => "name", "from" => "user", "where" => ["name" => "jclain1"]]));
|
||||
self::assertSame([
|
||||
"name" => "jclain1",
|
||||
], $sqlite->one(["select", "cols" => ["name"], "from" => "user", "where" => ["name" => "jclain1"]]));
|
||||
self::assertSame([
|
||||
"plouf" => "jclain1",
|
||||
], $sqlite->one(["select", "cols" => ["plouf" => "name"], "from" => "user", "where" => ["name" => "jclain1"]]));
|
||||
}
|
||||
|
||||
function testSelectGroupBy() {
|
||||
$sqlite = new Sqlite(":memory:", [
|
||||
"migrate" => "create table user (name varchar, amount integer)",
|
||||
]);
|
||||
$sqlite->exec(["insert into user", "values" => ["name" => "jclain1", "amount" => 1]]);
|
||||
$sqlite->exec(["insert into user", "values" => ["name" => "jclain2", "amount" => 1]]);
|
||||
$sqlite->exec(["insert into user", "values" => ["name" => "jclain5", "amount" => 2]]);
|
||||
$sqlite->exec(["insert into user", "values" => ["name" => "fclain7", "amount" => 2]]);
|
||||
$sqlite->exec(["insert into user", "values" => ["name" => "fclain9", "amount" => 2]]);
|
||||
$sqlite->exec(["insert into user", "values" => ["name" => "fclain10", "amount" => 3]]);
|
||||
|
||||
self::assertSame([
|
||||
["count" => 2],
|
||||
], iterator_to_array($sqlite->all(["select count(name) as count from user", "group by" => ["amount"], "having" => ["count(name) = 2"]])));
|
||||
}
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
<?php
|
||||
namespace nur\sery\db\sqlite;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class _queryTest extends TestCase {
|
||||
function testParseConds(): void {
|
||||
$sql = $params = null;
|
||||
_query_base::parse_conds(null, $sql, $params);
|
||||
self::assertNull($sql);
|
||||
self::assertNull($params);
|
||||
|
||||
$sql = $params = null;
|
||||
_query_base::parse_conds([], $sql, $params);
|
||||
self::assertNull($sql);
|
||||
self::assertNull($params);
|
||||
|
||||
$sql = $params = null;
|
||||
_query_base::parse_conds(["col" => null], $sql, $params);
|
||||
self::assertSame(["col is null"], $sql);
|
||||
self::assertNull($params);
|
||||
|
||||
$sql = $params = null;
|
||||
_query_base::parse_conds(["col = 'value'"], $sql, $params);
|
||||
self::assertSame(["col = 'value'"], $sql);
|
||||
self::assertNull($params);
|
||||
|
||||
$sql = $params = null;
|
||||
_query_base::parse_conds([["col = 'value'"]], $sql, $params);
|
||||
self::assertSame(["col = 'value'"], $sql);
|
||||
self::assertNull($params);
|
||||
|
||||
$sql = $params = null;
|
||||
_query_base::parse_conds(["int" => 42, "string" => "value"], $sql, $params);
|
||||
self::assertSame(["(int = :int and string = :string)"], $sql);
|
||||
self::assertSame(["int" => 42, "string" => "value"], $params);
|
||||
|
||||
$sql = $params = null;
|
||||
_query_base::parse_conds(["or", "int" => 42, "string" => "value"], $sql, $params);
|
||||
self::assertSame(["(int = :int or string = :string)"], $sql);
|
||||
self::assertSame(["int" => 42, "string" => "value"], $params);
|
||||
|
||||
$sql = $params = null;
|
||||
_query_base::parse_conds([["int" => 42, "string" => "value"], ["int" => 24, "string" => "eulav"]], $sql, $params);
|
||||
self::assertSame(["((int = :int and string = :string) and (int = :int2 and string = :string2))"], $sql);
|
||||
self::assertSame(["int" => 42, "string" => "value", "int2" => 24, "string2" => "eulav"], $params);
|
||||
|
||||
$sql = $params = null;
|
||||
_query_base::parse_conds(["int" => ["is null"], "string" => ["<>", "value"]], $sql, $params);
|
||||
self::assertSame(["(int is null and string <> :string)"], $sql);
|
||||
self::assertSame(["string" => "value"], $params);
|
||||
|
||||
$sql = $params = null;
|
||||
_query_base::parse_conds(["col" => ["between", "lower", "upper"]], $sql, $params);
|
||||
self::assertSame(["col between :col and :col2"], $sql);
|
||||
self::assertSame(["col" => "lower", "col2" => "upper"], $params);
|
||||
|
||||
$sql = $params = null;
|
||||
_query_base::parse_conds(["col" => ["in", "one"]], $sql, $params);
|
||||
self::assertSame(["col in (:col)"], $sql);
|
||||
self::assertSame(["col" => "one"], $params);
|
||||
|
||||
$sql = $params = null;
|
||||
_query_base::parse_conds(["col" => ["in", ["one", "two"]]], $sql, $params);
|
||||
self::assertSame(["col in (:col, :col2)"], $sql);
|
||||
self::assertSame(["col" => "one", "col2" => "two"], $params);
|
||||
|
||||
$sql = $params = null;
|
||||
_query_base::parse_conds(["col" => ["=", ["one", "two"]]], $sql, $params);
|
||||
self::assertSame(["col = :col and col = :col2"], $sql);
|
||||
self::assertSame(["col" => "one", "col2" => "two"], $params);
|
||||
|
||||
$sql = $params = null;
|
||||
_query_base::parse_conds(["or", "col" => ["=", ["one", "two"]]], $sql, $params);
|
||||
self::assertSame(["col = :col or col = :col2"], $sql);
|
||||
self::assertSame(["col" => "one", "col2" => "two"], $params);
|
||||
|
||||
$sql = $params = null;
|
||||
_query_base::parse_conds(["col" => ["<>", ["one", "two"]]], $sql, $params);
|
||||
self::assertSame(["col <> :col and col <> :col2"], $sql);
|
||||
self::assertSame(["col" => "one", "col2" => "two"], $params);
|
||||
|
||||
$sql = $params = null;
|
||||
_query_base::parse_conds(["or", "col" => ["<>", ["one", "two"]]], $sql, $params);
|
||||
self::assertSame(["col <> :col or col <> :col2"], $sql);
|
||||
self::assertSame(["col" => "one", "col2" => "two"], $params);
|
||||
}
|
||||
|
||||
function testParseValues(): void {
|
||||
$sql = $params = null;
|
||||
_query_base::parse_set_values(null, $sql, $params);
|
||||
self::assertNull($sql);
|
||||
self::assertNull($params);
|
||||
|
||||
$sql = $params = null;
|
||||
_query_base::parse_set_values([], $sql, $params);
|
||||
self::assertNull($sql);
|
||||
self::assertNull($params);
|
||||
|
||||
$sql = $params = null;
|
||||
_query_base::parse_set_values(["col = 'value'"], $sql, $params);
|
||||
self::assertSame(["col = 'value'"], $sql);
|
||||
self::assertNull($params);
|
||||
|
||||
$sql = $params = null;
|
||||
_query_base::parse_set_values([["col = 'value'"]], $sql, $params);
|
||||
self::assertSame(["col = 'value'"], $sql);
|
||||
self::assertNull($params);
|
||||
|
||||
$sql = $params = null;
|
||||
_query_base::parse_set_values(["int" => 42, "string" => "value"], $sql, $params);
|
||||
self::assertSame(["int = :int", "string = :string"], $sql);
|
||||
self::assertSame(["int" => 42, "string" => "value"], $params);
|
||||
|
||||
$sql = $params = null;
|
||||
_query_base::parse_set_values(["int" => 42, "string" => "value"], $sql, $params);
|
||||
self::assertSame(["int = :int", "string = :string"], $sql);
|
||||
self::assertSame(["int" => 42, "string" => "value"], $params);
|
||||
|
||||
$sql = $params = null;
|
||||
_query_base::parse_set_values([["int" => 42, "string" => "value"], ["int" => 24, "string" => "eulav"]], $sql, $params);
|
||||
self::assertSame(["int = :int", "string = :string", "int = :int2", "string = :string2"], $sql);
|
||||
self::assertSame(["int" => 42, "string" => "value", "int2" => 24, "string2" => "eulav"], $params);
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
<?php
|
||||
namespace nur\sery\file\base;
|
||||
|
||||
use nur\sery\file\FileReader;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class FileReaderTest extends TestCase {
|
||||
function testIgnoreBom() {
|
||||
# la lecture avec et sans BOM doit être identique
|
||||
## sans BOM
|
||||
$reader = new FileReader(__DIR__ . '/impl/sans_bom.txt');
|
||||
self::assertSame("0123456789", $reader->fread(10));
|
||||
self::assertSame(10, $reader->ftell());
|
||||
$reader->seek(30);
|
||||
self::assertSame("abcdefghij", $reader->fread(10));
|
||||
self::assertSame(40, $reader->ftell());
|
||||
$reader->seek(10);
|
||||
self::assertSame("ABCDEFGHIJ", $reader->fread(10));
|
||||
self::assertSame(20, $reader->ftell());
|
||||
$reader->seek(40);
|
||||
self::assertSame("0123456789\n", $reader->getContents());
|
||||
$reader->close();
|
||||
## avec BOM
|
||||
$reader = new FileReader(__DIR__ . '/impl/avec_bom.txt');
|
||||
self::assertSame("0123456789", $reader->fread(10));
|
||||
self::assertSame(10, $reader->ftell());
|
||||
$reader->seek(30);
|
||||
self::assertSame("abcdefghij", $reader->fread(10));
|
||||
self::assertSame(40, $reader->ftell());
|
||||
$reader->seek(10);
|
||||
self::assertSame("ABCDEFGHIJ", $reader->fread(10));
|
||||
self::assertSame(20, $reader->ftell());
|
||||
$reader->seek(40);
|
||||
self::assertSame("0123456789\n", $reader->getContents());
|
||||
$reader->close();
|
||||
}
|
||||
|
||||
function testCsvAutoParams() {
|
||||
$reader = new FileReader(__DIR__ . '/impl/msexcel.csv');
|
||||
self::assertSame(["nom", "prenom", "age"], $reader->fgetcsv());
|
||||
self::assertSame(["clain", "jephte", "50"], $reader->fgetcsv());
|
||||
self::assertNull($reader->fgetcsv());
|
||||
$reader->close();
|
||||
|
||||
$reader = new FileReader(__DIR__ . '/impl/ooffice.csv');
|
||||
self::assertSame(["nom", "prenom", "age"], $reader->fgetcsv());
|
||||
self::assertSame(["clain", "jephte", "50"], $reader->fgetcsv());
|
||||
self::assertNull($reader->fgetcsv());
|
||||
$reader->close();
|
||||
|
||||
$reader = new FileReader(__DIR__ . '/impl/weird.tsv');
|
||||
self::assertSame(["nom", "prenom", "age"], $reader->fgetcsv());
|
||||
self::assertSame(["clain", "jephte", "50"], $reader->fgetcsv());
|
||||
self::assertNull($reader->fgetcsv());
|
||||
$reader->close();
|
||||
|
||||
$reader = new FileReader(__DIR__ . '/impl/avec_bom.csv');
|
||||
self::assertSame(["nom", "prenom", "age"], $reader->fgetcsv());
|
||||
self::assertSame(["clain", "jephte", "50"], $reader->fgetcsv());
|
||||
self::assertNull($reader->fgetcsv());
|
||||
$reader->close();
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
nom,prenom,age
|
||||
clain,jephte,50
|
|
@ -1 +0,0 @@
|
||||
0123456789ABCDEFGHIJ0123456789abcdefghij0123456789
|
@ -1,2 +0,0 @@
|
||||
nom;prenom;age
|
||||
clain;jephte;50
|
|
@ -1,2 +0,0 @@
|
||||
nom,prenom,age
|
||||
clain,jephte,50
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user