Compare commits

...

75 Commits

Author SHA1 Message Date
68f5dc5209 modifs.mineures sans commentaires 2025-04-10 00:27:05 +04:00
f005692cd8 déplacer nur/sery/wip et nur/sery dans nulib 2025-04-04 06:35:07 +04:00
d844bd03ad modifs.mineures sans commentaires 2025-04-02 22:21:16 +04:00
c4e02d5bcf afficher la version de l'application 2025-04-02 18:06:51 +04:00
2a92a9a07e modifs.mineures sans commentaires 2025-03-28 16:17:12 +04:00
c274adb6e6 modifs.mineures sans commentaires 2025-03-28 15:38:57 +04:00
ff02ffdf4f modifs.mineures sans commentaires 2025-03-27 15:58:47 +04:00
9109e0fe39 modifs.mineures sans commentaires 2025-03-24 13:46:51 +04:00
fd1ebaf611 modifs.mineures sans commentaires 2025-03-23 08:19:20 +04:00
26817d2826 modifs.mineures sans commentaires 2025-03-23 08:06:40 +04:00
7257654753 modifs.mineures sans commentaires 2025-03-22 16:42:42 +04:00
e2bec38540 modifs.mineures sans commentaires 2025-03-21 07:45:22 +04:00
c2ec23be30 modifs.mineures sans commentaires 2025-03-20 09:57:07 +04:00
741a807420 modifs.mineures sans commentaires 2025-03-20 09:38:01 +04:00
e9f4826a94 modifs.mineures sans commentaires 2025-03-20 09:36:01 +04:00
fd9b3b29bc modifs.mineures sans commentaires 2025-03-20 08:41:31 +04:00
f37a53cfbd modifs.mineures sans commentaires 2025-03-20 08:01:50 +04:00
6e8245dbcb modifs.mineures sans commentaires 2025-03-19 20:34:46 +04:00
f4e252a6e0 modifs.mineures sans commentaires 2025-03-19 19:32:27 +04:00
19f6f9c9e1 modifs.mineures sans commentaires 2025-03-19 18:31:49 +04:00
baa770d969 modifs.mineures sans commentaires 2025-03-19 17:05:26 +04:00
c5bfa3940c modifs.mineures sans commentaires 2025-03-19 17:03:12 +04:00
39af99ffa4 modifs.mineures sans commentaires 2025-03-19 16:56:36 +04:00
1fc4e7637b modifs.mineures sans commentaires 2025-03-19 16:10:26 +04:00
ef7e8551aa modifs.mineures sans commentaires 2025-03-19 13:49:36 +04:00
9328aac9e9 modifs.mineures sans commentaires 2025-03-19 10:06:53 +04:00
62fc315b9e modifs.mineures sans commentaires 2025-03-19 08:37:17 +04:00
b9e91bd917 modifs.mineures sans commentaires 2025-03-19 07:18:41 +04:00
3608b02749 modifs.mineures sans commentaires 2025-03-19 06:38:19 +04:00
d148850c12 modifs.mineures sans commentaires 2025-03-18 17:37:55 +04:00
189c7aba68 modifs.mineures sans commentaires 2025-03-18 13:30:02 +04:00
91e6c0dcd2 modifs.mineures sans commentaires 2025-03-18 10:48:50 +04:00
bb311708d7 modifs.mineures sans commentaires 2025-03-18 10:43:43 +04:00
1feffb6c0f modifs.mineures sans commentaires 2025-03-18 10:35:28 +04:00
62987d576e <pman>Init changelog & version 0.4.1p82 2025-03-17 17:24:49 +04:00
5decd631e2 <pman>deps de dev 2025-03-17 17:20:12 +04:00
c98afaa98c <pman>Intégration de la branche rel74-0.4.1 2025-03-17 17:20:07 +04:00
3994855c7e <pman>deps de dist 2025-03-17 17:20:07 +04:00
6ba0a16e84 <pman>Init changelog & version 0.4.1p74 2025-03-17 17:20:02 +04:00
56fda96c78 changer le type de variables gérées par EnvConfig 2025-03-17 16:34:29 +04:00
d6078e8b52 modifs.mineures sans commentaires 2025-03-17 13:56:32 +04:00
69203352d8 modifs.mineures sans commentaires 2025-03-17 11:43:05 +04:00
26b483a29f modifs.mineures sans commentaires 2025-03-17 10:49:25 +04:00
6e93c7da62 modifs.mineures sans commentaires 2025-03-16 18:02:08 +04:00
66397a3bd0 <pman>Init changelog & version 0.4.0p82 2025-03-14 15:47:20 +04:00
eec9215431 <pman>deps de dev 2025-03-14 15:44:52 +04:00
1340c90d62 <pman>Intégration de la branche rel74-0.4.0 2025-03-14 15:44:47 +04:00
07a6d91385 <pman>deps de dist 2025-03-14 15:44:47 +04:00
41bcb07367 <pman>Init changelog & version 0.4.0p74 2025-03-14 15:44:41 +04:00
1ad63a92a9 <pman>Init changelog & version 0.3.0p82 2025-03-14 15:44:24 +04:00
cb1918e1fd maj deps 2025-03-14 15:43:14 +04:00
8a7b60e2d3 modifs.mineures sans commentaires 2025-03-11 17:42:52 +04:00
50cf0eca33 modifs.mineures sans commentaires 2025-03-10 17:44:44 +04:00
4b84f11f99 début assocSchema 2025-03-10 16:40:55 +04:00
9cf0e8045a modifs.mineures sans commentaires 2025-03-10 11:08:36 +04:00
08d5327afa modifs.mineures sans commentaires 2025-03-10 04:31:35 +04:00
4335a63d76 modifs.mineures sans commentaires 2025-03-08 12:25:53 +04:00
7227cd7bdd maj TODO 2025-03-07 20:06:10 +04:00
bb5b30480e modifs.mineures sans commentaires 2025-03-07 19:59:23 +04:00
c5c4119e69 modifs.mineures sans commentaires 2025-03-06 16:29:18 +04:00
089e4872aa renommer dest en value; tenir compte de la valeur par défaut 2025-03-06 16:22:12 +04:00
fc523bfb6c instancier type le plus vite possible 2025-03-06 15:32:49 +04:00
e1b4ef4cc3 modifs.mineures sans commentaires 2025-03-06 12:27:59 +04:00
a705dd61c4 modifs.mineures sans commentaires 2025-03-06 11:26:59 +04:00
f8eec57055 renommer Value en Wrapper 2025-03-06 09:05:10 +04:00
74487a0ab9 modifs.mineures sans commentaires 2025-03-05 07:50:38 +04:00
68023b72ee modifs.mineures sans commentaires 2025-03-04 14:04:09 +04:00
1aa266b509 tstring et trawstring 2025-03-04 09:52:23 +04:00
1685a40906 maj projet 2025-03-04 08:08:03 +04:00
998d353758 modifs.mineures sans commentaires 2025-03-04 06:53:55 +04:00
1a5ca79ef7 support des colonnes 2025-03-04 06:53:19 +04:00
2812046b4b maj projet 2025-03-02 19:20:45 +04:00
9438aaf396 ajout config .pman.yml 2025-03-02 15:40:10 +04:00
394ea62bd4 <pman>deps de dev 2025-03-01 13:46:46 +04:00
70a151296b <pman>Intégration de la branche rel74-0.3.0 2025-03-01 13:46:02 +04:00
125 changed files with 4452 additions and 4380 deletions

16
.composer.pman.yml Normal file
View 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

View File

@ -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
View File

@ -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
View File

@ -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>

View File

@ -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
'

View File

@ -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

View File

@ -1 +1 @@
0.3.0
0.4.1

View File

@ -3,6 +3,18 @@
"type": "library",
"description": "espace de maturation pour les librairies",
"repositories": [
{
"type": "path",
"url": "../nulib"
},
{
"type": "path",
"url": "../nulib-spout"
},
{
"type": "path",
"url": "../nulib-phpss"
},
{
"type": "composer",
"url": "https://repos.univ-reunion.fr/composer"
@ -18,9 +30,9 @@
"php": "^7.4"
},
"require-dev": {
"nulib/php": "^0.3.0p74",
"nulib/spout": "^0.3.0p74",
"nulib/phpss": "^0.3.0p74",
"nulib/php": "^7.4-dev",
"nulib/spout": "^7.4-dev",
"nulib/phpss": "^7.4-dev",
"nulib/tests": "^7.4",
"ext-posix": "*",
"ext-pcntl": "*",
@ -52,9 +64,7 @@
},
"autoload": {
"psr-4": {
"nulib\\": "src_glue",
"nur\\sery\\wip\\": "src",
"nur\\sery\\": "src_app",
"nulib\\": "src",
"nur\\": "nur_src"
},
"files": [
@ -63,7 +73,7 @@
},
"autoload-dev": {
"psr-4": {
"nur\\sery\\": "tests",
"nulib\\": "tests",
"nur\\": "nur_tests"
}
},

6
composer.lock generated
View File

@ -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": "*",

View File

@ -2,6 +2,6 @@
<?php
require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php';
use nur\sery\tools\SteamTrainApp;
use nulib\tools\SteamTrainApp;
SteamTrainApp::run();

View File

@ -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

View File

@ -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];
}

View File

@ -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 */

View File

@ -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;

View File

@ -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(),
]),

View File

@ -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;

View File

@ -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 {

View File

@ -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

View File

@ -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 {
}
}

View 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;
}

View 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);
}
}

View File

@ -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
]);
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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);
}
}

View File

@ -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);
}
}

View 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];
}
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -1,5 +1,5 @@
<?php
namespace nur\sery\wip\schema;
namespace nulib\schema;
class OldSchema {
/**

View File

@ -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

View File

@ -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;
}

View File

@ -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);

View File

@ -1,5 +1,5 @@
<?php
namespace nur\sery\wip\schema;
namespace nulib\schema;
use Exception;

View File

@ -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

View File

@ -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
View 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);
}
}

View 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;
}

View File

@ -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;}
}

View File

@ -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);
}
}

View File

@ -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 {
}
}

View 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);
}
}

View 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;
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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 {
}
}

View 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 {
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View 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;
}
}

View File

@ -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(),
]);
}
}

View File

@ -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(),
]);
}
}

View File

@ -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);
}
}

View File

@ -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(),
]);
}
}

View File

@ -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"); }
}

View File

@ -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;
}

View File

@ -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;
}
}

View 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);
}
}

View File

@ -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;
}
}

View 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;
}
}

View 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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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 {
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View 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 {
}
}

View 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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View 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);
}
}

View File

@ -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
View 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;
}
}

View 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);
}
}

View File

@ -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;
}

View File

@ -0,0 +1,9 @@
<?php
namespace nulib\schema\types;
class ttext extends trawstring {
const NAME = "text";
const TRIM = true;
const NORM_NL = true;
}

View File

@ -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) {

View File

@ -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

View File

@ -1,5 +0,0 @@
<?php
namespace nulib;
class app extends \nur\sery\app {
}

View File

@ -1,5 +0,0 @@
<?php
namespace nulib\app\cli;
abstract class Application extends \nur\sery\app\cli\Application {
}

View File

@ -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"]));
}
}

View File

@ -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__.'/..';

View File

@ -1 +0,0 @@
/capacitor.db*

View File

@ -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);
}
}

View File

@ -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"]])));
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -1,2 +0,0 @@
nom,prenom,age
clain,jephte,50
1 nom prenom age
2 clain jephte 50

View File

@ -1 +0,0 @@
0123456789ABCDEFGHIJ0123456789abcdefghij0123456789

View File

@ -1,2 +0,0 @@
nom;prenom;age
clain;jephte;50
1 nom prenom age
2 clain jephte 50

View File

@ -1,2 +0,0 @@
nom,prenom,age
clain,jephte,50
1 nom prenom age
2 clain jephte 50

Some files were not shown because too many files have changed in this diff Show More