Compare commits

..

24 Commits

Author SHA1 Message Date
b4e28ade02 modifs.mineures sans commentaires 2025-04-19 09:06:26 +04:00
d63a0cb704 modifs.mineures sans commentaires 2025-04-18 21:33:30 +04:00
91beea9c29 modifs.mineures sans commentaires 2025-04-16 19:22:47 +04:00
0e9be5f221 bug 2025-04-16 17:13:16 +04:00
5e141b575e pman: ajout des clés match_require et match_require-dev 2025-04-16 14:44:04 +04:00
8ee13a85c0 modifs.mineures sans commentaires 2025-04-16 12:26:30 +04:00
2af417a193 modifs.mineures sans commentaires 2025-04-16 12:19:21 +04:00
d4cc8bfa42 config pman composer 2025-04-16 12:19:05 +04:00
ca129dfda4 modifs.mineures sans commentaires 2025-04-15 12:28:02 +04:00
2a50167241 modifs.mineures sans commentaires 2025-04-15 12:20:21 +04:00
146461a184 modifs.mineures sans commentaires 2025-04-15 12:12:03 +04:00
2a46c12e08 modifs.mineures sans commentaires 2025-04-14 18:23:15 +04:00
d241ce6561 ajout PgsqlStorage 2025-04-14 10:42:30 +04:00
86136e75a5 modifs.mineures sans commentaires 2025-04-13 18:09:24 +04:00
64c872cf3f modifs.mineures sans commentaires 2025-04-11 18:20:27 +04:00
ebbd9e06c0 modifs.mineures sans commentaires 2025-04-10 15:27:19 +04:00
5c6d55ed46 maj ordre func 2025-04-10 15:04:37 +04:00
bab9ba81fe début pgsql 2025-04-10 14:33:24 +04:00
ecd01777c1 migration de nur_func à func 2025-04-10 09:42:09 +04:00
bd1f901b70 réorganiser le code de génération sql 2025-04-09 23:18:06 +04:00
1536e091fb améliorations func 2025-04-03 06:22:29 +04:00
a8d55d329a modifs.mineures sans commentaires 2025-03-28 16:17:25 +04:00
60ab13ff84 modifs.mineures sans commentaires 2025-03-28 15:39:05 +04:00
f2614385fe <pman>Init changelog & version 0.4.1p82 2025-03-25 08:48:09 +04:00
83 changed files with 1896 additions and 2264 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
/test_*
/.composer.lock.runphp
.~lock*#

View File

@ -1,11 +1,11 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
UPSTREAM=dev74
DEVELOP=dev82
FEATURE=wip82/
RELEASE=rel82-
MAIN=dist82
TAG_SUFFIX=p82
HOTFIX=hotf82-
UPSTREAM=
DEVELOP=dev74
FEATURE=wip74/
RELEASE=rel74-
MAIN=dist74
TAG_SUFFIX=p74
HOTFIX=hotf74-
DIST=
NOAUTO=

View File

@ -4,5 +4,5 @@
RUNPHP=
# Si RUNPHP n'est pas défini, les variables suivantes peuvent être définies
DIST=d12
DIST=d11
#REGISTRY=pubdocker.univ-reunion.fr/dist

View File

@ -91,6 +91,48 @@ function init_config_action() {
_push_branches
}
function _init_composer() {
if [ ! -f .composer.pman.yml -o -n "$ForceCreate" ]; then
ac_set_tmpfile config
cat >"$config" <<EOF
# -*- coding: utf-8 mode: yaml -*- vim:sw=2:sts=2:et:ai:si:sta:fenc=utf-8
composer:
match_prefix:
match_prefix-dev:
profiles: [ dev, dist ]
dev:
link: true
require:
reqire-dev:
dist:
link: false
require:
reqire-dev:
EOF
"${EDITOR:-nano}" "$config"
[ -s "$config" ] || return 1
cp "$config" .composer.pman.yml
git add .composer.pman.yml
fi
return 0
}
function init_composer_action() {
local -a push_branches; local config
[ -f .composer.pman.yml -a -z "$ForceCreate" ] && die "La configuration pman composer a déjà été initialisée"
resolve_should_push
_init_composer || exit_with ewarn "Initialisation de la configuration annulée"
git commit -m "configuration pman composer"
push_branches+=("$CurrentBranch")
_push_branches
}
function _ensure_main_branch() {
[ -n "$MAIN" ] || die "La branche MAIN n'a pas été définie"
[ -n "$MainBranch" ] || die "$MAIN: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
@ -108,7 +150,7 @@ $MAIN: une branche du même nom existe dans l'origine
function _ensure_develop_branch() {
[ -n "$DEVELOP" ] || die "La branche DEVELOP n'a pas été définie"
[ -n "$DevelopBranch" ] || die "$DEVELOP: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
[ "$1" == init -o -n "$DevelopBranch" ] || die "$DEVELOP: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
}
function init_develop_action() {
@ -119,7 +161,7 @@ function init_develop_action() {
$DEVELOP: une branche du même nom existe dans l'origine
git checkout $DEVELOP"
_ensure_main_branch
_ensure_develop_branch
_ensure_develop_branch init
resolve_should_push
@ -137,7 +179,7 @@ $DEVELOP: une branche du même nom existe dans l'origine
function _ensure_upstream_branch() {
[ -n "$UPSTREAM" ] || die "La branche UPSTREAM n'a pas été définie"
[ -n "$UpstreamBranch" ] || die "$UPSTREAM: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
[ "$1" == init -o -n "$UpstreamBranch" ] || die "$UPSTREAM: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
}
function init_upstream_action() {
@ -148,7 +190,7 @@ function init_upstream_action() {
$UPSTREAM: une branche du même nom existe dans l'origine
git checkout $UPSTREAM"
_ensure_develop_branch
_ensure_upstream_branch
_ensure_upstream_branch init
resolve_should_push
@ -182,7 +224,7 @@ $UPSTREAM: une branche du même nom existe dans l'origine
function _ensure_dist_branch() {
[ -n "$DIST" ] || die "La branche DIST n'a pas été définie"
[ -n "$DistBranch" ] || die "$DIST: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
[ "$1" == init -o -n "$DistBranch" ] || die "$DIST: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
}
function init_dist_action() {
@ -193,7 +235,7 @@ function init_dist_action() {
$DIST: une branche du même nom existe dans l'origine
git checkout $DIST"
_ensure_main_branch
_ensure_dist_branch
_ensure_dist_branch init
resolve_should_push
@ -242,6 +284,7 @@ function init_action() {
case "$what" in
init|repo|r) init_repo_action "$@";;
config) init_config_action "$@";;
composer) init_composer_action "$@";;
main|m) checkout_main_action;;
develop|dev|d) init_develop_action "$@";;
upstream|up|u) init_upstream_action "$@";;
@ -263,7 +306,8 @@ Origin=
ForceCreate=
args=(
"gérer un projet git"
"repo|config|develop|upstream|dist
"repo|config|composer
develop|upstream|dist
INITIALISATION
@ -272,6 +316,7 @@ configurer certaines branches du dépôt si elles n'existent pas déjà
repo
initialiser un dépôt vide et créer les branches $MAIN et $DEVELOP
develop
créer la branche $DEVELOP
upstream

View File

@ -15,15 +15,17 @@
}
},
"require": {
"symfony/yaml": "^7.1",
"symfony/yaml": "^5.0",
"ext-json": "*",
"php": "^8.2"
"php": "^7.4"
},
"require-dev": {
"nulib/tests": "^8.2",
"nulib/tests": "^7.4",
"ext-posix": "*",
"ext-pcntl": "*",
"ext-curl": "*",
"ext-pdo": "*",
"ext-pgsql": "*",
"ext-sqlite3": "*"
},
"autoload": {

4
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "266a079e97f3ceecc2cc0a84d6b9743b",
"content-hash": "a8b9dc80255663640bda855729ef2d47",
"packages": [
{
"name": "symfony/deprecation-contracts",
@ -2022,6 +2022,8 @@
"ext-posix": "*",
"ext-pcntl": "*",
"ext-curl": "*",
"ext-pdo": "*",
"ext-pgsql": "*",
"ext-sqlite3": "*"
},
"plugin-api-version": "2.2.0"

View File

@ -2,7 +2,7 @@
namespace nulib;
use ArrayAccess;
use nulib\php\nur_func;
use nulib\php\func;
use Traversable;
/**
@ -348,12 +348,12 @@ class cl {
#############################################################################
static final function map(callable $callback, ?iterable $array): array {
static final function map($func, ?iterable $array): array {
$result = [];
if ($array !== null) {
$ctx = nur_func::_prepare($callback);
$func = func::with($func);
foreach ($array as $key => $value) {
$result[$key] = nur_func::_call($ctx, [$value, $key]);
$result[$key] = $func->invoke([$value, $key]);
}
}
return $result;

View File

@ -1,7 +1,7 @@
<?php
namespace nulib\db;
use nulib\php\nur_func;
use nulib\php\func;
use nulib\ValueException;
use Traversable;
@ -87,7 +87,7 @@ class Capacitor implements ITransactor {
if ($func !== null) {
$commited = false;
try {
nur_func::call($func, $this);
func::call($func, $this);
if ($commit) {
$this->commit();
$commited = true;
@ -120,10 +120,6 @@ class Capacitor implements ITransactor {
if ($db->inTransaction()) $db->rollback();
}
function getCreateSql(): string {
return $this->storage->_getCreateSql($this->channel);
}
function exists(): bool {
return $this->storage->_exists($this->channel);
}

View File

@ -17,6 +17,8 @@ class CapacitorChannel {
const PRIMARY_KEYS = null;
const MIGRATION = null;
const MANAGE_TRANSACTIONS = true;
const EACH_COMMIT_THRESHOLD = 100;
@ -63,15 +65,29 @@ class CapacitorChannel {
$this->created = false;
$columnDefinitions = cl::withn(static::COLUMN_DEFINITIONS);
$primaryKeys = cl::withn(static::PRIMARY_KEYS);
if ($primaryKeys === null && $columnDefinitions !== null) {
$migration = cl::withn(static::MIGRATION);
if ($columnDefinitions !== null) {
# mettre à jour la liste des clés primaires et des migrations
$index = 0;
foreach ($columnDefinitions as $col => $def) {
if ($col === $index) {
# si définition séquentielle, seules les définitions de clé
# primaires sont supportées
$index++;
if (preg_match('/\bprimary\s+key\s+\((.+)\)/i', $def, $ms)) {
$primaryKeys = preg_split('/\s*,\s*/', trim($ms[1]));
}
} elseif (is_array($def)) {
# tableau: c'est une migration
$def = implode(" ", $def);
if ($def) {
$migration["add_$col"] = "alter table $tableName add column $col $def";
} else {
$migration["drop_$col"] = "alter table $tableName drop column $col";
}
} elseif (is_scalar($def)) {
# chaine: c'est une définition
$def = strval($def);
if (preg_match('/\bprimary\s+key\b/i', $def)) {
$primaryKeys[] = $col;
}
@ -80,6 +96,7 @@ class CapacitorChannel {
}
$this->columnDefinitions = $columnDefinitions;
$this->primaryKeys = $primaryKeys;
$this->migration = $migration;
}
protected string $name;
@ -192,6 +209,12 @@ class CapacitorChannel {
return $this->columnDefinitions;
}
protected ?array $migration;
function getMigration(): ?array {
return $this->migration;
}
protected ?array $primaryKeys;
function getPrimaryKeys(): ?array {
@ -245,9 +268,6 @@ class CapacitorChannel {
return $serial !== null? unserialize($serial): null;
}
const SERIAL_DEFINITION = "mediumtext";
const SUM_DEFINITION = "varchar(40)";
final function sum(?string $serial, $value=null): ?string {
if ($serial === null) $serial = $this->serialize($value);
return $serial !== null? sha1($serial): null;

View File

@ -2,8 +2,9 @@
namespace nulib\db;
use nulib\cl;
use nulib\db\_private\_migration;
use nulib\db\cache\cache;
use nulib\php\nur_func;
use nulib\php\func;
use nulib\ValueException;
use Traversable;
@ -35,14 +36,18 @@ abstract class CapacitorStorage {
/** DOIT être défini dans les classes dérivées */
const PRIMARY_KEY_DEFINITION = null;
const SERDATA_DEFINITION = "mediumtext";
const SERSUM_DEFINITION = "varchar(40)";
const SERTS_DEFINITION = "datetime";
const COLUMN_DEFINITIONS = [
"item__" => CapacitorChannel::SERIAL_DEFINITION,
"item__sum_" => CapacitorChannel::SUM_DEFINITION,
"created_" => "datetime",
"modified_" => "datetime",
"item__" => "serdata",
"item__sum_" => "sersum",
"created_" => "serts",
"modified_" => "serts",
];
protected function ColumnDefinitions(CapacitorChannel $channel): array {
protected function ColumnDefinitions(CapacitorChannel $channel, bool $ignoreMigrations=false): array {
$definitions = [];
if ($channel->getPrimaryKeys() === null) {
$definitions[] = static::PRIMARY_KEY_DEFINITION;
@ -56,16 +61,31 @@ abstract class CapacitorStorage {
$constraints = [];
$index = 0;
foreach ($tmp as $col => $def) {
switch ($def) {
case "serdata": $def = static::SERDATA_DEFINITION; break;
case "sersum": $def = static::SERSUM_DEFINITION; break;
case "serts": $def = static::SERTS_DEFINITION; break;
}
if ($col === $index) {
$index++;
$constraints[] = $def;
} else {
} elseif (is_array($def)) {
# éventuellement, ignorer les migrations
$def = implode(" ", $def);
if ($def && !$ignoreMigrations) {
$definitions[$col] = $def;
}
} elseif (is_scalar($def)) {
$definitions[$col] = strval($def);
}
}
return cl::merge($definitions, $constraints);
}
protected function getMigration(CapacitorChannel $channel): ?array {
return $channel->getMigration();
}
/** sérialiser les valeurs qui doivent l'être dans $values */
protected function serialize(CapacitorChannel $channel, ?array $values): ?array {
if ($values === null) return null;
@ -128,11 +148,10 @@ abstract class CapacitorStorage {
}
protected function _createSql(CapacitorChannel $channel): array {
$cols = $this->ColumnDefinitions($channel);
return [
"create table if not exists",
"table" => $channel->getTableName(),
"cols" => $cols,
"cols" => $this->ColumnDefinitions($channel, true),
];
}
@ -147,20 +166,45 @@ $sql;
EOT;
}
abstract function _getCreateSql(CapacitorChannel $channel): string;
abstract function _getMigration(CapacitorChannel $channel): _migration;
/** obtenir la requête SQL utilisée pour créer la table */
function getCreateSql(?string $channel): string {
return $this->_getCreateSql($this->getChannel($channel));
const CHANNELS_TABLE = "_channels";
const CHANNELS_COLS = [
"name" => "varchar primary key",
"table_name" => "varchar",
"class_name" => "varchar",
];
protected function _createChannelsSql(): array {
return [
"create table if not exists",
"table" => static::CHANNELS_TABLE,
"cols" => static::CHANNELS_COLS,
];
}
protected function _addToChannelsSql(CapacitorChannel $channel): array {
return [
"insert",
"into" => static::CHANNELS_TABLE,
"values" => [
"name" => $channel->getName(),
"table_name" => $channel->getTableName(),
"class_name" => get_class($channel),
],
];
}
protected function _afterCreate(CapacitorChannel $channel): void {
$db = $this->db();
$db->exec($this->_createChannelsSql());
$db->exec($this->_addToChannelsSql($channel));
}
protected function _create(CapacitorChannel $channel): void {
$channel->ensureSetup();
if (!$channel->isCreated()) {
$this->db->exec($this->_createSql($channel));
$this->_getMigration($channel)->migrate($this->db());
$this->_afterCreate($channel);
$channel->setCreated();
}
@ -183,12 +227,28 @@ EOT;
}
protected function _beforeReset(CapacitorChannel $channel): void {
$db = $this->db;
$name = $channel->getName();
$db->exec([
"delete",
"from" => _migration::MIGRATION_TABLE,
"where" => [
"channel" => $name,
],
]);
$db->exec([
"delete",
"from" => static::CHANNELS_TABLE,
"where" => [
"name" => $name,
],
]);
}
/** supprimer le canal spécifié */
function _reset(CapacitorChannel $channel, bool $recreate=false): void {
$this->_beforeReset($channel);
$this->db->exec([
$this->db()->exec([
"drop table if exists",
$channel->getTableName(),
]);
@ -230,10 +290,7 @@ EOT;
$db = $this->db();
$args ??= [];
$initFunc = [$channel, "getItemValues"];
$initArgs = $args;
nur_func::ensure_func($initFunc, null, $initArgs);
$values = nur_func::call($initFunc, $item, ...$initArgs);
$values = func::call([$channel, "getItemValues"], $item, ...$args);
if ($values === [false]) return 0;
$row = cl::merge(
@ -259,9 +316,7 @@ EOT;
"modified_" => $now,
]);
$insert = true;
$initFunc = [$channel, "onCreate"];
$initArgs = $args;
nur_func::ensure_func($initFunc, null, $initArgs);
$initFunc = func::with([$channel, "onCreate"], $args);
$values = $this->unserialize($channel, $row);
$pvalues = null;
} else {
@ -276,14 +331,12 @@ EOT;
} else {
$row = cl::merge($prow, $row);
}
$initFunc = [$channel, "onUpdate"];
$initArgs = $args;
nur_func::ensure_func($initFunc, null, $initArgs);
$initFunc = func::with([$channel, "onUpdate"], $args);
$values = $this->unserialize($channel, $row);
$pvalues = $this->unserialize($channel, $prow);
}
$updates = nur_func::call($initFunc, $item, $values, $pvalues, ...$initArgs);
$updates = $initFunc->prependArgs([$item, $values, $pvalues])->invoke();
if ($updates === [false]) return 0;
if (is_array($updates) && $updates) {
if ($insert === null) $insert = false;
@ -295,8 +348,10 @@ EOT;
}
if ($func !== null) {
nur_func::ensure_func($func, $channel, $args);
$updates = nur_func::call($func, $item, $values, $pvalues, ...$args);
$updates = func::with($func)
->prependArgs([$item, $values, $pvalues])
->bind($channel, true)
->invoke();
if ($updates === [false]) return 0;
if (is_array($updates) && $updates) {
if ($insert === null) $insert = false;
@ -510,8 +565,7 @@ EOT;
function _each(CapacitorChannel $channel, $filter, $func, ?array $args, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
$this->_create($channel);
if ($func === null) $func = CapacitorChannel::onEach;
nur_func::ensure_func($func, $channel, $args);
$onEach = nur_func::_prepare($func);
$onEach = func::with($func)->bind($channel, true);
$db = $this->db();
# si on est déjà dans une transaction, désactiver la gestion des transactions
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
@ -528,7 +582,7 @@ EOT;
$all = $this->_allCached("each", $channel, $filter, $mergeQuery);
foreach ($all as $values) {
$rowIds = $this->getRowIds($channel, $values);
$updates = nur_func::_call($onEach, [$values["item"], $values, ...$args]);
$updates = $onEach->invoke([$values["item"], $values, ...$args]);
if (is_array($updates) && $updates) {
if (!array_key_exists("modified_", $updates)) {
$updates["modified_"] = date("Y-m-d H:i:s");
@ -579,8 +633,7 @@ EOT;
function _delete(CapacitorChannel $channel, $filter, $func, ?array $args): int {
$this->_create($channel);
if ($func === null) $func = CapacitorChannel::onDelete;
nur_func::ensure_func($func, $channel, $args);
$onEach = nur_func::_prepare($func);
$onEach = func::with($func)->bind($channel, true);
$db = $this->db();
# si on est déjà dans une transaction, désactiver la gestion des transactions
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
@ -596,7 +649,7 @@ EOT;
$all = $this->_allCached("delete", $channel, $filter);
foreach ($all as $values) {
$rowIds = $this->getRowIds($channel, $values);
$delete = boolval(nur_func::_call($onEach, [$values["item"], $values, ...$args]));
$delete = boolval($onEach->invoke([$values["item"], $values, ...$args]));
if ($delete) {
$db->exec([
"delete",

View File

@ -15,5 +15,9 @@ interface IDatabase extends ITransactor {
function one($query, ?array $params=null): ?array;
/**
* si $primaryKeys est fourni, le résultat est indexé sur la(es) colonne(s)
* spécifiée(s)
*/
function all($query, ?array $params=null, $primaryKeys=null): iterable;
}

View File

@ -11,12 +11,13 @@ interface ITransactor {
*/
function willUpdate(...$transactors): self;
/** Indiquer si une transaction est en cours */
function inTransaction(): bool;
/**
* démarrer une transaction
*
* si $func!==null, l'apppeler. ensuite, si $commit===true, commiter la
* si $func!==null, l'apppeler. ensuite, si $commit===true, valider la
* transaction. si une erreur se produit lors de l'appel de la fonction,
* annuler la transaction
*
@ -24,7 +25,9 @@ interface ITransactor {
*/
function beginTransaction(?callable $func=null, bool $commit=true): void;
/** valider la transaction */
function commit(): void;
/** annuler la transaction */
function rollback(): void;
}

View File

@ -1,39 +0,0 @@
<?php
namespace nulib\db\_private;
trait Tcreate {
static function isa(string $sql): bool {
//return preg_match("/^create(?:\s+table)?\b/i", $sql);
#XXX implémentation minimale
return preg_match("/^create\s+table\b/i", $sql);
}
static function parse(array $query, ?array &$bindings=null): string {
#XXX implémentation minimale
$sql = [self::merge_seq($query)];
## préfixe
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
## table
$sql[] = $query["table"];
## columns
$cols = $query["cols"];
$index = 0;
foreach ($cols as $col => &$definition) {
if ($col === $index) {
$index++;
} else {
$definition = "$col $definition";
}
}; unset($definition);
$sql[] = "(\n ".implode("\n, ", $cols)."\n)";
## suffixe
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
## fin de la requête
return implode(" ", $sql);
}
}

View File

@ -1,38 +0,0 @@
<?php
namespace nulib\db\_private;
trait Tdelete {
static function isa(string $sql): bool {
return preg_match("/^delete(?:\s+from)?\b/i", $sql);
}
static function parse(array $query, ?array &$bindings=null): string {
#XXX implémentation minimale
$tmpsql = self::merge_seq($query);
self::consume('delete(?:\s+from)?\b', $tmpsql);
$sql = ["delete from", $tmpsql];
## préfixe
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
## table
$sql[] = $query["from"];
## where
$where = $query["where"] ?? null;
if ($where !== null) {
self::parse_conds($where, $wheresql, $bindings);
if ($wheresql) {
$sql[] = "where";
$sql[] = implode(" and ", $wheresql);
}
}
## suffixe
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
## fin de la requête
return implode(" ", $sql);
}
}

View File

@ -1,18 +0,0 @@
<?php
namespace nulib\db\_private;
use nulib\cl;
use nulib\ValueException;
trait Tgeneric {
static function isa(string $sql): bool {
return preg_match('/^(?:drop\s+table)\b/i', $sql);
}
static function parse(array $query, ?array &$bindings=null): string {
if (!cl::is_list($query)) {
throw new ValueException("Seuls les tableaux séquentiels sont supportés");
}
return self::merge_seq($query);
}
}

View File

@ -1,82 +0,0 @@
<?php
namespace nulib\db\_private;
use nulib\cl;
use nulib\ValueException;
trait Tinsert {
static function isa(string $sql): bool {
return preg_match("/^insert\b/i", $sql);
}
/**
* parser une chaine de la forme
* "insert [into] [TABLE] [(COLS)] [values (VALUES)]"
*/
static function parse(array $query, ?array &$bindings=null): string {
# fusionner d'abord toutes les parties séquentielles
$usersql = $tmpsql = self::merge_seq($query);
### vérifier la présence des parties nécessaires
$sql = [];
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
## insert
self::consume('(insert(?:\s+or\s+(?:ignore|replace))?)\s*', $tmpsql, $ms);
$sql[] = $ms[1];
## into
self::consume('into\s*', $tmpsql);
$sql[] = "into";
$into = $query["into"] ?? null;
if (self::consume('([a-z_][a-z0-9_]*)\s*', $tmpsql, $ms)) {
if ($into === null) $into = $ms[1];
$sql[] = $into;
} elseif ($into !== null) {
$sql[] = $into;
} else {
throw new ValueException("expected table name: $usersql");
}
## cols & values
$usercols = [];
$uservalues = [];
if (self::consume('\(([^)]*)\)\s*', $tmpsql, $ms)) {
$usercols = array_merge($usercols, preg_split("/\s*,\s*/", $ms[1]));
}
$cols = cl::withn($query["cols"] ?? null);
$values = cl::withn($query["values"] ?? null);
$schema = $query["schema"] ?? null;
if ($cols === null) {
if ($usercols) {
$cols = $usercols;
} elseif ($values) {
$cols = array_keys($values);
$usercols = array_merge($usercols, $cols);
} elseif ($schema && is_array($schema)) {
#XXX implémenter support AssocSchema
$cols = array_keys($schema);
$usercols = array_merge($usercols, $cols);
}
}
if (self::consume('values\s+\(\s*(.*)\s*\)\s*', $tmpsql, $ms)) {
if ($ms[1]) $uservalues[] = $ms[1];
}
if ($cols !== null && !$uservalues) {
if (!$usercols) $usercols = $cols;
foreach ($cols as $col) {
$uservalues[] = ":$col";
$bindings[$col] = $values[$col] ?? null;
}
}
$sql[] = "(" . implode(", ", $usercols) . ")";
$sql[] = "values (" . implode(", ", $uservalues) . ")";
## suffixe
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
## fin de la requête
self::check_eof($tmpsql, $usersql);
return implode(" ", $sql);
}
}

View File

@ -1,168 +0,0 @@
<?php
namespace nulib\db\_private;
use nulib\cl;
use nulib\str;
use nulib\ValueException;
trait Tselect {
static function isa(string $sql): bool {
return preg_match("/^select\b/i", $sql);
}
private static function add_prefix(string $col, ?string $prefix): string {
if ($prefix === null) return $col;
if (strpos($col, ".") !== false) return $col;
return "$prefix$col";
}
/**
* parser une chaine de la forme
* "select [COLS] [from TABLE] [where CONDS] [order by ORDERS] [group by GROUPS] [having CONDS]"
*/
static function parse(array $query, ?array &$bindings=null): string {
# fusionner d'abord toutes les parties séquentielles
$usersql = $tmpsql = self::merge_seq($query);
### vérifier la présence des parties nécessaires
$sql = [];
## préfixe
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
## select
self::consume('(select(?:\s*distinct)?)\s*', $tmpsql, $ms);
$sql[] = $ms[1];
## cols
$usercols = [];
if (self::consume('(.*?)\s*(?=$|\bfrom\b)', $tmpsql, $ms)) {
if ($ms[1]) $usercols[] = $ms[1];
}
$colPrefix = $query["col_prefix"] ?? null;
if ($colPrefix !== null) str::add_suffix($colPrefix, ".");
$tmpcols = cl::withn($query["cols"] ?? null);
$schema = $query["schema"] ?? null;
if ($tmpcols !== null) {
$cols = [];
$index = 0;
foreach ($tmpcols as $key => $col) {
if ($key === $index) {
$index++;
$cols[] = $col;
$usercols[] = self::add_prefix($col, $colPrefix);
} else {
$cols[] = $key;
$usercols[] = self::add_prefix($col, $colPrefix)." as $key";
}
}
} else {
$cols = null;
if ($schema && is_array($schema) && !in_array("*", $usercols)) {
$cols = array_keys($schema);
foreach ($cols as $col) {
$usercols[] = self::add_prefix($col, $colPrefix);
}
}
}
if (!$usercols && !$cols) $usercols = [self::add_prefix("*", $colPrefix)];
$sql[] = implode(", ", $usercols);
## from
$from = $query["from"] ?? null;
if (self::consume('from\s+([a-z_][a-z0-9_]*)\s*(?=;?\s*$|\bwhere\b)', $tmpsql, $ms)) {
if ($from === null) $from = $ms[1];
$sql[] = "from";
$sql[] = $from;
} elseif ($from !== null) {
$sql[] = "from";
$sql[] = $from;
} else {
throw new ValueException("expected table name: $usersql");
}
## where
$userwhere = [];
if (self::consume('where\b\s*(.*?)(?=;?\s*$|\border\s+by\b)', $tmpsql, $ms)) {
if ($ms[1]) $userwhere[] = $ms[1];
}
$where = cl::withn($query["where"] ?? null);
if ($where !== null) self::parse_conds($where, $userwhere, $bindings);
if ($userwhere) {
$sql[] = "where";
$sql[] = implode(" and ", $userwhere);
}
## order by
$userorderby = [];
if (self::consume('order\s+by\b\s*(.*?)(?=;?\s*$|\bgroup\s+by\b)', $tmpsql, $ms)) {
if ($ms[1]) $userorderby[] = $ms[1];
}
$orderby = cl::withn($query["order by"] ?? null);
if ($orderby !== null) {
$index = 0;
foreach ($orderby as $key => $value) {
if ($key === $index) {
$userorderby[] = $value;
$index++;
} else {
if ($value === null) $value = false;
if (!is_bool($value)) {
$userorderby[] = "$key $value";
} elseif ($value) {
$userorderby[] = $key;
}
}
}
}
if ($userorderby) {
$sql[] = "order by";
$sql[] = implode(", ", $userorderby);
}
## group by
$usergroupby = [];
if (self::consume('group\s+by\b\s*(.*?)(?=;?\s*$|\bhaving\b)', $tmpsql, $ms)) {
if ($ms[1]) $usergroupby[] = $ms[1];
}
$groupby = cl::withn($query["group by"] ?? null);
if ($groupby !== null) {
$index = 0;
foreach ($groupby as $key => $value) {
if ($key === $index) {
$usergroupby[] = $value;
$index++;
} else {
if ($value === null) $value = false;
if (!is_bool($value)) {
$usergroupby[] = "$key $value";
} elseif ($value) {
$usergroupby[] = $key;
}
}
}
}
if ($usergroupby) {
$sql[] = "group by";
$sql[] = implode(", ", $usergroupby);
}
## having
$userhaving = [];
if (self::consume('having\b\s*(.*?)(?=;?\s*$)', $tmpsql, $ms)) {
if ($ms[1]) $userhaving[] = $ms[1];
}
$having = cl::withn($query["having"] ?? null);
if ($having !== null) self::parse_conds($having, $userhaving, $bindings);
if ($userhaving) {
$sql[] = "having";
$sql[] = implode(" and ", $userhaving);
}
## suffixe
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
## fin de la requête
self::check_eof($tmpsql, $usersql);
return implode(" ", $sql);
}
}

View File

@ -1,40 +0,0 @@
<?php
namespace nulib\db\_private;
trait Tupdate {
static function isa(string $sql): bool {
return preg_match("/^update\b/i", $sql);
}
static function parse(array $query, ?array &$bindings=null): string {
#XXX implémentation minimale
$sql = [self::merge_seq($query)];
## préfixe
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
## table
$sql[] = $query["table"];
## set
self::parse_set_values($query["values"], $setsql, $bindings);
$sql[] = "set";
$sql[] = implode(", ", $setsql);
## where
$where = $query["where"] ?? null;
if ($where !== null) {
self::parse_conds($where, $wheresql, $bindings);
if ($wheresql) {
$sql[] = "where";
$sql[] = implode(" and ", $wheresql);
}
}
## suffixe
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
## fin de la requête
return implode(" ", $sql);
}
}

View File

@ -5,255 +5,57 @@ use nulib\cl;
use nulib\str;
use nulib\ValueException;
abstract class _base {
protected static function consume(string $pattern, string &$string, ?array &$ms=null): bool {
if (!preg_match("/^$pattern/i", $string, $ms)) return false;
$string = substr($string, strlen($ms[0]));
return true;
}
/** fusionner toutes les parties séquentielles d'une requête */
protected static function merge_seq(array $query): string {
$index = 0;
$sql = "";
foreach ($query as $key => $value) {
if ($key === $index) {
$index++;
if ($sql && !str::ends_with(" ", $sql) && !str::starts_with(" ", $value)) {
$sql .= " ";
}
$sql .= $value;
}
}
return $sql;
}
protected static function is_sep(&$cond): bool {
if (!is_string($cond)) return false;
if (!preg_match('/^\s*(and|or|not)\s*$/i', $cond, $ms)) return false;
$cond = $ms[1];
return true;
}
static function parse_conds(?array $conds, ?array &$sql, ?array &$bindings): void {
if (!$conds) return;
$sep = null;
$index = 0;
$condsql = [];
foreach ($conds as $key => $cond) {
if ($key === $index) {
## séquentiel
if ($index === 0 && self::is_sep($cond)) {
$sep = $cond;
} elseif (is_bool($cond)) {
# ignorer les valeurs true et false
} elseif (is_array($cond)) {
# condition récursive
self::parse_conds($cond, $condsql, $bindings);
abstract class _base extends _common {
protected static function verifix(&$sql, ?array &$bindings=null, ?array &$meta=null): void {
if (is_array($sql)) {
$prefix = $sql[0] ?? null;
if ($prefix === null) {
throw new ValueException("requête invalide");
} elseif (_create::isa($prefix)) {
$sql = _create::parse($sql, $bindings);
$meta = ["isa" => "create", "type" => "ddl"];
} elseif (_select::isa($prefix)) {
$sql = _select::parse($sql, $bindings);
$meta = ["isa" => "select", "type" => "dql"];
} elseif (_insert::isa($prefix)) {
$sql = _insert::parse($sql, $bindings);
$meta = ["isa" => "insert", "type" => "dml"];
} elseif (_update::isa($prefix)) {
$sql = _update::parse($sql, $bindings);
$meta = ["isa" => "update", "type" => "dml"];
} elseif (_delete::isa($prefix)) {
$sql = _delete::parse($sql, $bindings);
$meta = ["isa" => "delete", "type" => "dml"];
} elseif (_generic::isa($prefix)) {
$sql = _generic::parse($sql, $bindings);
$meta = ["isa" => "generic", "type" => null];
} else {
# condition litérale
$condsql[] = strval($cond);
}
$index++;
} elseif ($cond === false) {
## associatif
# condition litérale ignorée car condition false
} elseif ($cond === true) {
# condition litérale sélectionnée car condition true
$condsql[] = strval($key);
} else {
## associatif
# paramètre
$param0 = preg_replace('/^.+\./', "", $key);
$i = false;
if ($bindings !== null && array_key_exists($param0, $bindings)) {
$i = 2;
while (array_key_exists("$param0$i", $bindings)) {
$i++;
}
}
# value ou [operator, value]
$condprefix = $condsep = $condsuffix = null;
if (is_array($cond)) {
$condkey = 0;
$condkeys = array_keys($cond);
$op = null;
if (array_key_exists("op", $cond)) {
$op = $cond["op"];
} elseif (array_key_exists($condkey, $condkeys)) {
$op = $cond[$condkeys[$condkey]];
$condkey++;
}
$op = strtolower($op);
$condvalues = null;
switch ($op) {
case "between":
# ["between", $upper, $lower]
$condsep = " and ";
if (array_key_exists("lower", $cond)) {
$condvalues[] = $cond["lower"];
} elseif (array_key_exists($condkey, $condkeys)) {
$condvalues[] = $cond[$condkeys[$condkey]];
$condkey++;
}
if (array_key_exists("upper", $cond)) {
$condvalues[] = $cond["upper"];
} elseif (array_key_exists($condkey, $condkeys)) {
$condvalues[] = $cond[$condkeys[$condkey]];
$condkey++;
}
break;
case "any":
case "all":
case "not any":
case "not all":
# ["list", $values]
if ($op === "any" || $op === "all") {
$condprefix = $op;
$op = "=";
} elseif ($op === "not any" || $op === "not all") {
$condprefix = substr($op, strlen("not "));
$op = "<>";
}
$condprefix .= "(array[";
$condsep = ", ";
$condsuffix = "])";
$condvalues = null;
if (array_key_exists("values", $cond)) {
$condvalues = cl::with($cond["values"]);
} elseif (array_key_exists($condkey, $condkeys)) {
$condvalues = cl::with($cond[$condkeys[$condkey]]);
$condkey++;
}
break;
case "in":
# ["in", $values]
$condprefix = "(";
$condsep = ", ";
$condsuffix = ")";
$condvalues = null;
if (array_key_exists("values", $cond)) {
$condvalues = cl::with($cond["values"]);
} elseif (array_key_exists($condkey, $condkeys)) {
$condvalues = cl::with($cond[$condkeys[$condkey]]);
$condkey++;
}
break;
case "null":
case "is null":
$op = "is null";
break;
case "not null":
case "is not null":
$op = "is not null";
break;
default:
if (array_key_exists("value", $cond)) {
$condvalues = [$cond["value"]];
} elseif (array_key_exists($condkey, $condkeys)) {
$condvalues = [$cond[$condkeys[$condkey]]];
$condkey++;
}
}
} elseif ($cond !== null) {
$op = "=";
$condvalues = [$cond];
} else {
$op = "is null";
$condvalues = null;
}
$cond = [$key, $op];
if ($condvalues !== null) {
$parts = [];
foreach ($condvalues as $condvalue) {
if (is_array($condvalue)) {
$first = true;
foreach ($condvalue as $value) {
if ($first) {
$first = false;
} else {
if ($sep === null) $sep = "and";
$parts[] = " $sep ";
$parts[] = $key;
$parts[] = " $op ";
}
$param = "$param0$i";
$parts[] = ":$param";
$bindings[$param] = $value;
if ($i === false) $i = 2;
else $i++;
throw ValueException::invalid_kind($sql, "query");
}
} else {
$param = "$param0$i";
$parts[] = ":$param";
$bindings[$param] = $condvalue;
if ($i === false) $i = 2;
else $i++;
if (!is_string($sql)) $sql = strval($sql);
if (_create::isa($sql)) {
$meta = ["isa" => "create", "type" => "ddl"];
} elseif (_select::isa($sql)) {
$meta = ["isa" => "select", "type" => "dql"];
} elseif (_insert::isa($sql)) {
$meta = ["isa" => "insert", "type" => "dml"];
} elseif (_update::isa($sql)) {
$meta = ["isa" => "update", "type" => "dml"];
} elseif (_delete::isa($sql)) {
$meta = ["isa" => "delete", "type" => "dml"];
} elseif (_generic::isa($sql)) {
$meta = ["isa" => "generic", "type" => null];
} else {
$meta = ["isa" => "generic", "type" => null];
}
}
$cond[] = $condprefix.implode($condsep, $parts).$condsuffix;
}
$condsql[] = implode(" ", $cond);
}
}
if ($sep === null) $sep = "and";
$count = count($condsql);
if ($count > 1) {
$sql[] = "(" . implode(" $sep ", $condsql) . ")";
} elseif ($count == 1) {
$sql[] = $condsql[0];
}
}
static function parse_set_values(?array $values, ?array &$sql, ?array &$bindings): void {
if (!$values) return;
$index = 0;
$parts = [];
foreach ($values as $key => $part) {
if ($key === $index) {
## séquentiel
if (is_array($part)) {
# paramètres récursifs
self::parse_set_values($part, $parts, $bindings);
} else {
# paramètre litéral
$parts[] = strval($part);
static function with($sql, ?array $params=null): array {
static::verifix($sql, $params);
return [$sql, $params];
}
$index++;
} else {
## associatif
# paramètre
$param = $param0 = preg_replace('/^.+\./', "", $key);
if ($bindings !== null && array_key_exists($param0, $bindings)) {
$i = 2;
while (array_key_exists("$param0$i", $bindings)) {
$i++;
}
$param = "$param0$i";
}
# value
$value = $part;
$part = [$key, "="];
if ($value === null) {
$part[] = "null";
} else {
$part[] = ":$param";
$bindings[$param] = $value;
}
$parts[] = implode(" ", $part);
}
}
$sql = cl::merge($sql, $parts);
}
protected static function check_eof(string $tmpsql, string $usersql): void {
self::consume(';\s*', $tmpsql);
if ($tmpsql) {
throw new ValueException("unexpected value at end: $usersql");
}
}
abstract protected static function verifix(&$sql, ?array &$bindinds=null, ?array &$meta=null): void;
function __construct($sql, ?array $bindings=null) {
static::verifix($sql, $bindings, $meta);

View File

@ -0,0 +1,255 @@
<?php
namespace nulib\db\_private;
use nulib\cl;
use nulib\str;
use nulib\ValueException;
class _common {
protected static function consume(string $pattern, string &$string, ?array &$ms=null): bool {
if (!preg_match("/^$pattern/i", $string, $ms)) return false;
$string = substr($string, strlen($ms[0]));
return true;
}
/** fusionner toutes les parties séquentielles d'une requête */
protected static function merge_seq(array $query): string {
$index = 0;
$sql = "";
foreach ($query as $key => $value) {
if ($key === $index) {
$index++;
if ($sql && !str::ends_with(" ", $sql) && !str::starts_with(" ", $value)) {
$sql .= " ";
}
$sql .= $value;
}
}
return $sql;
}
protected static function is_sep(&$cond): bool {
if (!is_string($cond)) return false;
if (!preg_match('/^\s*(and|or|not)\s*$/i', $cond, $ms)) return false;
$cond = $ms[1];
return true;
}
static function parse_conds(?array $conds, ?array &$sql, ?array &$bindings): void {
if (!$conds) return;
$sep = null;
$index = 0;
$condsql = [];
foreach ($conds as $key => $cond) {
if ($key === $index) {
## séquentiel
if ($index === 0 && self::is_sep($cond)) {
$sep = $cond;
} elseif (is_bool($cond)) {
# ignorer les valeurs true et false
} elseif (is_array($cond)) {
# condition récursive
self::parse_conds($cond, $condsql, $bindings);
} else {
# condition litérale
$condsql[] = strval($cond);
}
$index++;
} elseif ($cond === false) {
## associatif
# condition litérale ignorée car condition false
} elseif ($cond === true) {
# condition litérale sélectionnée car condition true
$condsql[] = strval($key);
} else {
## associatif
# paramètre
$param0 = preg_replace('/^.+\./', "", $key);
$i = false;
if ($bindings !== null && array_key_exists($param0, $bindings)) {
$i = 2;
while (array_key_exists("$param0$i", $bindings)) {
$i++;
}
}
# value ou [operator, value]
$condprefix = $condsep = $condsuffix = null;
if (is_array($cond)) {
$condkey = 0;
$condkeys = array_keys($cond);
$op = null;
if (array_key_exists("op", $cond)) {
$op = $cond["op"];
} elseif (array_key_exists($condkey, $condkeys)) {
$op = $cond[$condkeys[$condkey]];
$condkey++;
}
$op = strtolower($op);
$condvalues = null;
switch ($op) {
case "between":
# ["between", $upper, $lower]
$condsep = " and ";
if (array_key_exists("lower", $cond)) {
$condvalues[] = $cond["lower"];
} elseif (array_key_exists($condkey, $condkeys)) {
$condvalues[] = $cond[$condkeys[$condkey]];
$condkey++;
}
if (array_key_exists("upper", $cond)) {
$condvalues[] = $cond["upper"];
} elseif (array_key_exists($condkey, $condkeys)) {
$condvalues[] = $cond[$condkeys[$condkey]];
$condkey++;
}
break;
case "any":
case "all":
case "not any":
case "not all":
# ["list", $values]
if ($op === "any" || $op === "all") {
$condprefix = $op;
$op = "=";
} elseif ($op === "not any" || $op === "not all") {
$condprefix = substr($op, strlen("not "));
$op = "<>";
}
$condprefix .= "(array[";
$condsep = ", ";
$condsuffix = "])";
$condvalues = null;
if (array_key_exists("values", $cond)) {
$condvalues = cl::with($cond["values"]);
} elseif (array_key_exists($condkey, $condkeys)) {
$condvalues = cl::with($cond[$condkeys[$condkey]]);
$condkey++;
}
break;
case "in":
# ["in", $values]
$condprefix = "(";
$condsep = ", ";
$condsuffix = ")";
$condvalues = null;
if (array_key_exists("values", $cond)) {
$condvalues = cl::with($cond["values"]);
} elseif (array_key_exists($condkey, $condkeys)) {
$condvalues = cl::with($cond[$condkeys[$condkey]]);
$condkey++;
}
break;
case "null":
case "is null":
$op = "is null";
break;
case "not null":
case "is not null":
$op = "is not null";
break;
default:
if (array_key_exists("value", $cond)) {
$condvalues = [$cond["value"]];
} elseif (array_key_exists($condkey, $condkeys)) {
$condvalues = [$cond[$condkeys[$condkey]]];
$condkey++;
}
}
} elseif ($cond !== null) {
$op = "=";
$condvalues = [$cond];
} else {
$op = "is null";
$condvalues = null;
}
$cond = [$key, $op];
if ($condvalues !== null) {
$parts = [];
foreach ($condvalues as $condvalue) {
if (is_array($condvalue)) {
$first = true;
foreach ($condvalue as $value) {
if ($first) {
$first = false;
} else {
if ($sep === null) $sep = "and";
$parts[] = " $sep ";
$parts[] = $key;
$parts[] = " $op ";
}
$param = "$param0$i";
$parts[] = ":$param";
$bindings[$param] = $value;
if ($i === false) $i = 2;
else $i++;
}
} else {
$param = "$param0$i";
$parts[] = ":$param";
$bindings[$param] = $condvalue;
if ($i === false) $i = 2;
else $i++;
}
}
$cond[] = $condprefix.implode($condsep, $parts).$condsuffix;
}
$condsql[] = implode(" ", $cond);
}
}
if ($sep === null) $sep = "and";
$count = count($condsql);
if ($count > 1) {
$sql[] = "(" . implode(" $sep ", $condsql) . ")";
} elseif ($count == 1) {
$sql[] = $condsql[0];
}
}
static function parse_set_values(?array $values, ?array &$sql, ?array &$bindings): void {
if (!$values) return;
$index = 0;
$parts = [];
foreach ($values as $key => $part) {
if ($key === $index) {
## séquentiel
if (is_array($part)) {
# paramètres récursifs
self::parse_set_values($part, $parts, $bindings);
} else {
# paramètre litéral
$parts[] = strval($part);
}
$index++;
} else {
## associatif
# paramètre
$param = $param0 = preg_replace('/^.+\./', "", $key);
if ($bindings !== null && array_key_exists($param0, $bindings)) {
$i = 2;
while (array_key_exists("$param0$i", $bindings)) {
$i++;
}
$param = "$param0$i";
}
# value
$value = $part;
$part = [$key, "="];
if ($value === null) {
$part[] = "null";
} else {
$part[] = ":$param";
$bindings[$param] = $value;
}
$parts[] = implode(" ", $part);
}
}
$sql = cl::merge($sql, $parts);
}
protected static function check_eof(string $tmpsql, string $usersql): void {
self::consume(';\s*', $tmpsql);
if ($tmpsql) {
throw new ValueException("unexpected value at end: $usersql");
}
}
}

View File

@ -1,7 +1,8 @@
<?php
namespace nulib\db\pdo;
namespace nulib\db\_private;
use nulib\php\nur_func;
use nulib\db\IDatabase;
use nulib\php\func;
class _config {
static function with($configs): self {
@ -23,13 +24,12 @@ class _config {
/** @var array */
protected $configs;
function configure(Pdo $pdo): void {
function configure(IDatabase $db): void {
foreach ($this->configs as $key => $config) {
if (is_string($config) && !nur_func::is_method($config)) {
$pdo->exec($config);
if (is_string($config) && !func::is_method($config)) {
$db->exec($config);
} else {
nur_func::ensure_func($config, $this, $args);
nur_func::call($config, $pdo, $key, ...$args);
func::with($config)->bind($this, true)->invoke([$db, $key]);
}
}
}

View File

@ -1,7 +1,7 @@
<?php
namespace nulib\db\_private;
class _create {
class _create extends _common {
const SCHEMA = [
"prefix" => "?string",
"table" => "string",
@ -9,4 +9,46 @@ class _create {
"cols" => "?array",
"suffix" => "?string",
];
static function isa(string $sql): bool {
#XXX implémentation minimale
return preg_match("/^create(?:\s+table)?\b/i", $sql);
}
static function parse(array $query, ?array &$bindings=null): string {
#XXX implémentation minimale
$tmpsql = self::merge_seq($query);
self::consume('create(?:\s+table)?\b', $tmpsql);
$sql = ["create table"];
if ($tmpsql) $sql[] = $tmpsql;
## préfixe
$prefix = $query["prefix"] ?? null;
if ($prefix !== null) $sql[] = $prefix;
## table
$table = $query["table"] ?? null;
if ($table !== null) $sql[] = $table;
## columns
$cols = $query["cols"] ?? null;
if ($cols !== null) {
$index = 0;
foreach ($cols as $col => &$definition) {
if ($col === $index) {
$index++;
} else {
$definition = "$col $definition";
}
}; unset($definition);
$sql[] = "(\n ".implode("\n, ", $cols)."\n)";
}
## suffixe
$suffix = $query["suffix"] ?? null;
if ($suffix !== null) $sql[] = $suffix;
## fin de la requête
return implode(" ", $sql);
}
}

View File

@ -1,11 +1,48 @@
<?php
namespace nulib\db\_private;
class _delete {
class _delete extends _common {
const SCHEMA = [
"prefix" => "?string",
"from" => "?string",
"where" => "?array",
"suffix" => "?string",
];
static function isa(string $sql): bool {
return preg_match("/^delete(?:\s+from)?\b/i", $sql);
}
static function parse(array $query, ?array &$bindings=null): string {
#XXX implémentation minimale
$tmpsql = self::merge_seq($query);
self::consume('delete(?:\s+from)?\b', $tmpsql);
$sql = ["delete from"];
if ($tmpsql) $sql[] = $tmpsql;
## préfixe
$prefix = $query["prefix"] ?? null;
if ($prefix !== null) $sql[] = $prefix;
## table
$from = $query["from"] ?? null;
if ($from !== null) $sql[] = $from;
## where
$where = $query["where"] ?? null;
if ($where !== null) {
self::parse_conds($where, $wheresql, $bindings);
if ($wheresql) {
$sql[] = "where";
$sql[] = implode(" and ", $wheresql);
}
}
## suffixe
$suffix = $query["suffix"] ?? null;
if ($suffix !== null) $sql[] = $suffix;
## fin de la requête
return implode(" ", $sql);
}
}

View File

@ -1,7 +1,21 @@
<?php
namespace nulib\db\_private;
class _generic {
use nulib\cl;
use nulib\ValueException;
class _generic extends _common {
const SCHEMA = [
];
static function isa(string $sql): bool {
return preg_match('/^drop\s+table\b/i', $sql);
}
static function parse(array $query, ?array &$bindings=null): string {
if (!cl::is_list($query)) {
throw new ValueException("Seuls les tableaux séquentiels sont supportés");
}
return self::merge_seq($query);
}
}

View File

@ -1,7 +1,10 @@
<?php
namespace nulib\db\_private;
class _insert {
use nulib\cl;
use nulib\ValueException;
class _insert extends _common {
const SCHEMA = [
"prefix" => "?string",
"into" => "?string",
@ -10,4 +13,79 @@ class _insert {
"values" => "?array",
"suffix" => "?string",
];
static function isa(string $sql): bool {
return preg_match("/^insert\b/i", $sql);
}
/**
* parser une chaine de la forme
* "insert [into] [TABLE] [(COLS)] [values (VALUES)]"
*/
static function parse(array $query, ?array &$bindings=null): string {
# fusionner d'abord toutes les parties séquentielles
$usersql = $tmpsql = self::merge_seq($query);
### vérifier la présence des parties nécessaires
$sql = [];
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
## insert
self::consume('(insert(?:\s+or\s+(?:ignore|replace))?)\s*', $tmpsql, $ms);
$sql[] = $ms[1];
## into
self::consume('into\s*', $tmpsql);
$sql[] = "into";
$into = $query["into"] ?? null;
if (self::consume('([a-z_][a-z0-9_]*)\s*', $tmpsql, $ms)) {
if ($into === null) $into = $ms[1];
$sql[] = $into;
} elseif ($into !== null) {
$sql[] = $into;
} else {
throw new ValueException("expected table name: $usersql");
}
## cols & values
$usercols = [];
$uservalues = [];
if (self::consume('\(([^)]*)\)\s*', $tmpsql, $ms)) {
$usercols = array_merge($usercols, preg_split("/\s*,\s*/", $ms[1]));
}
$cols = cl::withn($query["cols"] ?? null);
$values = cl::withn($query["values"] ?? null);
$schema = $query["schema"] ?? null;
if ($cols === null) {
if ($usercols) {
$cols = $usercols;
} elseif ($values) {
$cols = array_keys($values);
$usercols = array_merge($usercols, $cols);
} elseif ($schema && is_array($schema)) {
#XXX implémenter support AssocSchema
$cols = array_keys($schema);
$usercols = array_merge($usercols, $cols);
}
}
if (self::consume('values\s+\(\s*(.*)\s*\)\s*', $tmpsql, $ms)) {
if ($ms[1]) $uservalues[] = $ms[1];
}
if ($cols !== null && !$uservalues) {
if (!$usercols) $usercols = $cols;
foreach ($cols as $col) {
$uservalues[] = ":$col";
$bindings[$col] = $values[$col] ?? null;
}
}
$sql[] = "(" . implode(", ", $usercols) . ")";
$sql[] = "values (" . implode(", ", $uservalues) . ")";
## suffixe
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
## fin de la requête
self::check_eof($tmpsql, $usersql);
return implode(" ", $sql);
}
}

View File

@ -0,0 +1,72 @@
<?php
namespace nulib\db\_private;
use nulib\db\IDatabase;
use nulib\php\func;
abstract class _migration {
const MIGRATION = null;
function __construct($migrations, string $channel="", ?IDatabase $db=null) {
$this->db = $db;
$this->channel = $channel;
if ($migrations === null) $migrations = static::MIGRATION;
if ($migrations === null) $migrations = [];
elseif (is_string($migrations)) $migrations = [$migrations];
elseif (is_callable($migrations)) $migrations = [$migrations];
elseif (!is_array($migrations)) $migrations = [strval($migrations)];
$this->migrations = $migrations;
}
protected ?IDatabase $db;
protected string $channel;
const MIGRATION_TABLE = "_migration";
const MIGRATION_COLS = [
"channel" => "varchar not null",
"name" => "varchar not null",
"done" => "integer not null default 0",
"primary key (channel, name)",
];
protected function ensureTable(): void {
$this->db->exec([
"create table if not exists",
"table" => static::MIGRATION_TABLE,
"cols" => static::MIGRATION_COLS,
]);
}
protected function isMigrated(string $name): bool {
return boolval($this->db->get([
"select 1",
"from" => static::MIGRATION_TABLE,
"where" => [
"channel" => $this->channel,
"name" => $name,
"done" => 1,
],
]));
}
abstract protected function setMigrated(string $name, bool $done): void;
/** @var callable[]|string[] */
protected $migrations;
function migrate(?IDatabase $db=null): void {
$db = ($this->db ??= $db);
$this->ensureTable();
foreach ($this->migrations as $name => $migration) {
if ($this->isMigrated($name)) continue;
$this->setMigrated($name, false);
if (is_string($migration) || !func::is_callable($migration)) {
$db->exec($migration);
} else {
func::with($migration)->bind($this, true)->invoke([$db, $name]);
}
$this->setMigrated($name, true);
}
}
}

View File

@ -1,7 +1,11 @@
<?php
namespace nulib\db\_private;
class _select {
use nulib\cl;
use nulib\str;
use nulib\ValueException;
class _select extends _common {
const SCHEMA = [
"prefix" => "?string",
"schema" => "?array",
@ -14,4 +18,164 @@ class _select {
"having" => "?array",
"suffix" => "?string",
];
static function isa(string $sql): bool {
return preg_match("/^select\b/i", $sql);
}
private static function add_prefix(string $col, ?string $prefix): string {
if ($prefix === null) return $col;
if (strpos($col, ".") !== false) return $col;
return "$prefix$col";
}
/**
* parser une chaine de la forme
* "select [COLS] [from TABLE] [where CONDS] [order by ORDERS] [group by GROUPS] [having CONDS]"
*/
static function parse(array $query, ?array &$bindings=null): string {
# fusionner d'abord toutes les parties séquentielles
$usersql = $tmpsql = self::merge_seq($query);
### vérifier la présence des parties nécessaires
$sql = [];
## préfixe
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
## select
self::consume('(select(?:\s*distinct)?)\s*', $tmpsql, $ms);
$sql[] = $ms[1];
## cols
$usercols = [];
if (self::consume('(.*?)\s*(?=$|\bfrom\b)', $tmpsql, $ms)) {
if ($ms[1]) $usercols[] = $ms[1];
}
$colPrefix = $query["col_prefix"] ?? null;
if ($colPrefix !== null) str::add_suffix($colPrefix, ".");
$tmpcols = cl::withn($query["cols"] ?? null);
$schema = $query["schema"] ?? null;
if ($tmpcols !== null) {
$cols = [];
$index = 0;
foreach ($tmpcols as $key => $col) {
if ($key === $index) {
$index++;
$cols[] = $col;
$usercols[] = self::add_prefix($col, $colPrefix);
} else {
$cols[] = $key;
$usercols[] = self::add_prefix($col, $colPrefix)." as $key";
}
}
} else {
$cols = null;
if ($schema && is_array($schema) && !in_array("*", $usercols)) {
$cols = array_keys($schema);
foreach ($cols as $col) {
$usercols[] = self::add_prefix($col, $colPrefix);
}
}
}
if (!$usercols && !$cols) $usercols = [self::add_prefix("*", $colPrefix)];
$sql[] = implode(", ", $usercols);
## from
$from = $query["from"] ?? null;
if (self::consume('from\s+([a-z_][a-z0-9_]*)\s*(?=;?\s*$|\bwhere\b)', $tmpsql, $ms)) {
if ($from === null) $from = $ms[1];
$sql[] = "from";
$sql[] = $from;
} elseif ($from !== null) {
$sql[] = "from";
$sql[] = $from;
} else {
throw new ValueException("expected table name: $usersql");
}
## where
$userwhere = [];
if (self::consume('where\b\s*(.*?)(?=;?\s*$|\border\s+by\b)', $tmpsql, $ms)) {
if ($ms[1]) $userwhere[] = $ms[1];
}
$where = cl::withn($query["where"] ?? null);
if ($where !== null) self::parse_conds($where, $userwhere, $bindings);
if ($userwhere) {
$sql[] = "where";
$sql[] = implode(" and ", $userwhere);
}
## order by
$userorderby = [];
if (self::consume('order\s+by\b\s*(.*?)(?=;?\s*$|\bgroup\s+by\b)', $tmpsql, $ms)) {
if ($ms[1]) $userorderby[] = $ms[1];
}
$orderby = cl::withn($query["order by"] ?? null);
if ($orderby !== null) {
$index = 0;
foreach ($orderby as $key => $value) {
if ($key === $index) {
$userorderby[] = $value;
$index++;
} else {
if ($value === null) $value = false;
if (!is_bool($value)) {
$userorderby[] = "$key $value";
} elseif ($value) {
$userorderby[] = $key;
}
}
}
}
if ($userorderby) {
$sql[] = "order by";
$sql[] = implode(", ", $userorderby);
}
## group by
$usergroupby = [];
if (self::consume('group\s+by\b\s*(.*?)(?=;?\s*$|\bhaving\b)', $tmpsql, $ms)) {
if ($ms[1]) $usergroupby[] = $ms[1];
}
$groupby = cl::withn($query["group by"] ?? null);
if ($groupby !== null) {
$index = 0;
foreach ($groupby as $key => $value) {
if ($key === $index) {
$usergroupby[] = $value;
$index++;
} else {
if ($value === null) $value = false;
if (!is_bool($value)) {
$usergroupby[] = "$key $value";
} elseif ($value) {
$usergroupby[] = $key;
}
}
}
}
if ($usergroupby) {
$sql[] = "group by";
$sql[] = implode(", ", $usergroupby);
}
## having
$userhaving = [];
if (self::consume('having\b\s*(.*?)(?=;?\s*$)', $tmpsql, $ms)) {
if ($ms[1]) $userhaving[] = $ms[1];
}
$having = cl::withn($query["having"] ?? null);
if ($having !== null) self::parse_conds($having, $userhaving, $bindings);
if ($userhaving) {
$sql[] = "having";
$sql[] = implode(" and ", $userhaving);
}
## suffixe
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
## fin de la requête
self::check_eof($tmpsql, $usersql);
return implode(" ", $sql);
}
}

View File

@ -1,7 +1,7 @@
<?php
namespace nulib\db\_private;
class _update {
class _update extends _common {
const SCHEMA = [
"prefix" => "?string",
"table" => "?string",
@ -11,4 +11,43 @@ class _update {
"where" => "?array",
"suffix" => "?string",
];
static function isa(string $sql): bool {
return preg_match("/^update\b/i", $sql);
}
static function parse(array $query, ?array &$bindings=null): string {
#XXX implémentation minimale
$sql = [self::merge_seq($query)];
## préfixe
$prefix = $query["prefix"] ?? null;
if ($prefix !== null) $sql[] = $prefix;
## table
$table = $query["table"] ?? null;
if ($table !== null) $sql[] = $table;
## set
self::parse_set_values($query["values"], $setsql, $bindings);
$sql[] = "set";
$sql[] = implode(", ", $setsql);
## where
$where = $query["where"] ?? null;
if ($where !== null) {
self::parse_conds($where, $wheresql, $bindings);
if ($wheresql) {
$sql[] = "where";
$sql[] = implode(" and ", $wheresql);
}
}
## suffixe
$suffix = $query["suffix"] ?? null;
if ($suffix !== null) $sql[] = $suffix;
## fin de la requête
return implode(" ", $sql);
}
}

View File

@ -1,6 +1,7 @@
<?php
namespace nulib\db\mysql;
use nulib\cl;
use nulib\db\CapacitorChannel;
use nulib\db\CapacitorStorage;
@ -12,8 +13,7 @@ class MysqlStorage extends CapacitorStorage {
$this->db = Mysql::with($mysql);
}
/** @var Mysql */
protected $db;
protected Mysql $db;
function db(): Mysql {
return $this->db;
@ -23,17 +23,35 @@ class MysqlStorage extends CapacitorStorage {
"id_" => "integer primary key auto_increment",
];
function _getMigration(CapacitorChannel $channel): _mysqlMigration {
return new _mysqlMigration(cl::merge([
$this->_createSql($channel),
], $channel->getMigration()), $channel->getName());
}
function _getCreateSql(CapacitorChannel $channel): string {
$query = new _query_base($this->_createSql($channel));
$query = new _mysqlQuery($this->_createSql($channel));
return self::format_sql($channel, $query->getSql());
}
const CHANNELS_COLS = [
"name" => "varchar(255) primary key",
"table_name" => "varchar(64)",
"class_name" => "varchar(255)",
];
protected function _addToChannelsSql(CapacitorChannel $channel): array {
return cl::merge(parent::_addToChannelsSql($channel), [
"suffix" => "on duplicate key update name = name",
]);
}
function _exists(CapacitorChannel $channel): bool {
$db = $this->db;
$tableName = $db->get([
$mysql = $this->db;
$tableName = $mysql->get([
"select table_name from information_schema.tables",
"where" => [
"table_schema" => $db->getDbname(),
"table_schema" => $mysql->getDbname(),
"table_name" => $channel->getTableName(),
],
]);

View File

@ -0,0 +1,31 @@
<?php
namespace nulib\db\mysql;
use nulib\db\_private\_migration;
class _mysqlMigration extends _migration {
static function with($migration): self {
if ($migration instanceof self) return $migration;
else return new static($migration);
}
const MIGRATION_COLS = [
"channel" => "varchar(64) not null",
"name" => "varchar(64) not null",
"done" => "integer not null default 0",
"primary key (channel, name)",
];
protected function setMigrated(string $name, bool $done): void {
$this->db->exec([
"insert",
"into" => static::MIGRATION_TABLE,
"values" => [
"channel" => $this->channel,
"name" => $name,
"done" => $done? 1: 0,
],
"suffix" => "on duplicate key update done = :done",
]);
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace nulib\db\mysql;
use nulib\db\pdo\_pdoQuery;
class _mysqlQuery extends _pdoQuery {
const DEBUG_QUERIES = false;
}

View File

@ -1,52 +0,0 @@
<?php
namespace nulib\db\mysql;
use nulib\ValueException;
class _query_base extends \nulib\db\pdo\_query_base {
protected static function verifix(&$sql, ?array &$bindinds=null, ?array &$meta=null): void {
if (is_array($sql)) {
$prefix = $sql[0] ?? null;
if ($prefix === null) {
throw new ValueException("requête invalide");
} elseif (_query_create::isa($prefix)) {
$sql = _query_create::parse($sql, $bindinds);
$meta = ["isa" => "create", "type" => "ddl"];
} elseif (_query_select::isa($prefix)) {
$sql = _query_select::parse($sql, $bindinds);
$meta = ["isa" => "select", "type" => "dql"];
} elseif (_query_insert::isa($prefix)) {
$sql = _query_insert::parse($sql, $bindinds);
$meta = ["isa" => "insert", "type" => "dml"];
} elseif (_query_update::isa($prefix)) {
$sql = _query_update::parse($sql, $bindinds);
$meta = ["isa" => "update", "type" => "dml"];
} elseif (_query_delete::isa($prefix)) {
$sql = _query_delete::parse($sql, $bindinds);
$meta = ["isa" => "delete", "type" => "dml"];
} elseif (_query_generic::isa($prefix)) {
$sql = _query_generic::parse($sql, $bindinds);
$meta = ["isa" => "generic", "type" => null];
} else {
throw ValueException::invalid_kind($sql, "query");
}
} else {
if (!is_string($sql)) $sql = strval($sql);
if (_query_create::isa($sql)) {
$meta = ["isa" => "create", "type" => "ddl"];
} elseif (_query_select::isa($sql)) {
$meta = ["isa" => "select", "type" => "dql"];
} elseif (_query_insert::isa($sql)) {
$meta = ["isa" => "insert", "type" => "dml"];
} elseif (_query_update::isa($sql)) {
$meta = ["isa" => "update", "type" => "dml"];
} elseif (_query_delete::isa($sql)) {
$meta = ["isa" => "delete", "type" => "dml"];
} elseif (_query_generic::isa($sql)) {
$meta = ["isa" => "generic", "type" => null];
} else {
$meta = ["isa" => "generic", "type" => null];
}
}
}
}

View File

@ -1,10 +0,0 @@
<?php
namespace nulib\db\mysql;
use nulib\db\_private\_create;
use nulib\db\_private\Tcreate;
class _query_create extends _query_base {
use Tcreate;
const SCHEMA = _create::SCHEMA;
}

View File

@ -1,10 +0,0 @@
<?php
namespace nulib\db\mysql;
use nulib\db\_private\_delete;
use nulib\db\_private\Tdelete;
class _query_delete extends _query_base {
use Tdelete;
const SCHEMA = _delete::SCHEMA;
}

View File

@ -1,10 +0,0 @@
<?php
namespace nulib\db\mysql;
use nulib\db\_private\_generic;
use nulib\db\_private\Tgeneric;
class _query_generic extends _query_base {
use Tgeneric;
const SCHEMA = _generic::SCHEMA;
}

View File

@ -1,10 +0,0 @@
<?php
namespace nulib\db\mysql;
use nulib\db\_private\_insert;
use nulib\db\_private\Tinsert;
class _query_insert extends _query_base {
use Tinsert;
const SCHEMA = _insert::SCHEMA;
}

View File

@ -1,10 +0,0 @@
<?php
namespace nulib\db\mysql;
use nulib\db\_private\_select;
use nulib\db\_private\Tselect;
class _query_select extends _query_base {
use Tselect;
const SCHEMA = _select::SCHEMA;
}

View File

@ -1,10 +0,0 @@
<?php
namespace nulib\db\mysql;
use nulib\db\_private\_update;
use nulib\db\_private\Tupdate;
class _query_update extends _query_base {
use Tupdate;
const SCHEMA = _update::SCHEMA;
}

View File

@ -1,12 +0,0 @@
<?php
namespace nulib\db\mysql;
/**
* Class query: classe outil temporaire pour générer les requêtes
*/
class query extends _query_base {
static function with($sql, ?array $params=null): array {
self::verifix($sql, $params);
return [$sql, $params];
}
}

View File

@ -3,10 +3,11 @@ namespace nulib\db\pdo;
use Generator;
use nulib\cl;
use nulib\db\_private\_config;
use nulib\db\_private\Tvalues;
use nulib\db\IDatabase;
use nulib\db\ITransactor;
use nulib\php\nur_func;
use nulib\php\func;
use nulib\ValueException;
class Pdo implements IDatabase {
@ -21,7 +22,7 @@ class Pdo implements IDatabase {
"dbconn" => $pdo->dbconn,
"options" => $pdo->options,
"config" => $pdo->config,
"migrate" => $pdo->migration,
"migration" => $pdo->migration,
], $params));
} else {
return new static($pdo, $params);
@ -49,7 +50,7 @@ class Pdo implements IDatabase {
protected const CONFIG = null;
protected const MIGRATE = null;
protected const MIGRATION = null;
const dbconn_SCHEMA = [
"name" => "string",
@ -62,7 +63,7 @@ class Pdo implements IDatabase {
"options" => ["?array|callable"],
"replace_config" => ["?array|callable"],
"config" => ["?array|callable"],
"migrate" => ["?array|string|callable"],
"migration" => ["?array|string|callable"],
"auto_open" => ["bool", true],
];
@ -93,7 +94,7 @@ class Pdo implements IDatabase {
}
$this->config = $config;
# migrations
$this->migration = $params["migrate"] ?? static::MIGRATE;
$this->migration = $params["migration"] ?? static::MIGRATION;
#
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
if ($params["auto_open"] ?? $defaultAutoOpen) {
@ -104,7 +105,7 @@ class Pdo implements IDatabase {
protected ?array $dbconn;
/** @var array|callable */
protected array $options;
protected $options;
/** @var array|string|callable */
protected $config;
@ -119,8 +120,7 @@ class Pdo implements IDatabase {
$dbconn = $this->dbconn;
$options = $this->options;
if (is_callable($options)) {
nur_func::ensure_func($options, $this, $args);
$options = nur_func::call($options, ...$args);
$options = func::with($options)->bind($this, true)->invoke();
}
$this->db = new \PDO($dbconn["name"], $dbconn["user"], $dbconn["pass"], $options);
_config::with($this->config)->configure($this);
@ -143,21 +143,16 @@ class Pdo implements IDatabase {
return $this->db()->exec($query);
}
private static function is_insert(?string $sql): bool {
if ($sql === null) return false;
return preg_match('/^\s*insert\b/i', $sql);
}
function exec($query, ?array $params=null) {
$db = $this->db();
$query = new _query_base($query, $params);
if ($query->useStmt($db, $stmt, $sql)) {
$query = new _pdoQuery($query, $params);
if ($query->_use_stmt($db, $stmt, $sql)) {
if ($stmt->execute() === false) return false;
if ($query->isInsert()) return $db->lastInsertId();
else return $stmt->rowCount();
} else {
$rowCount = $db->exec($sql);
if (self::is_insert($sql)) return $db->lastInsertId();
if ($query->isInsert()) return $db->lastInsertId();
else return $rowCount;
}
}
@ -191,7 +186,7 @@ class Pdo implements IDatabase {
if ($func !== null) {
$commited = false;
try {
nur_func::call($func, $this);
func::call($func, $this);
if ($commit) {
$this->commit();
$commited = true;
@ -222,11 +217,11 @@ class Pdo implements IDatabase {
function get($query, ?array $params=null, bool $entireRow=false) {
$db = $this->db();
$query = new _query_base($query, $params);
$query = new _pdoQuery($query, $params);
$stmt = null;
try {
/** @var \PDOStatement $stmt */
if ($query->useStmt($db, $stmt, $sql)) {
if ($query->_use_stmt($db, $stmt, $sql)) {
if ($stmt->execute() === false) return null;
} else {
$stmt = $db->query($sql);
@ -245,22 +240,18 @@ class Pdo implements IDatabase {
return $this->get($query, $params, true);
}
/**
* si $primaryKeys est fourni, le résultat est indexé sur la(es) colonne(s)
* spécifiée(s)
*/
function all($query, ?array $params=null, $primaryKeys=null): Generator {
function all($query, ?array $params=null, $primaryKeys=null): iterable {
$db = $this->db();
$query = new _query_base($query, $params);
$query = new _pdoQuery($query, $params);
$stmt = null;
try {
/** @var \PDOStatement $stmt */
if ($query->useStmt($db, $stmt, $sql)) {
if ($query->_use_stmt($db, $stmt, $sql)) {
if ($stmt->execute() === false) return;
} else {
$stmt = $db->query($sql);
}
if ($primaryKeys !== null) $primaryKeys = cl::with($primaryKeys);
$primaryKeys = cl::withn($primaryKeys);
while (($row = $stmt->fetch(\PDO::FETCH_ASSOC)) !== false) {
$this->verifixRow($row);
if ($primaryKeys !== null) {

View File

@ -0,0 +1,30 @@
<?php
namespace nulib\db\pdo;
use nulib\db\_private\_base;
use nulib\db\_private\Tbindings;
use nulib\output\msg;
class _pdoQuery extends _base {
use Tbindings;
const DEBUG_QUERIES = false;
function _use_stmt(\PDO $db, ?\PDOStatement &$stmt=null, ?string &$sql=null): bool {
if (static::DEBUG_QUERIES) {#XXX
msg::info($this->sql);
//msg::info(var_export($this->bindings, true));
}
if ($this->bindings !== null) {
$stmt = $db->prepare($this->sql);
foreach ($this->bindings as $name => $value) {
$this->verifixBindings($value);
$stmt->bindValue($name, $value);
}
return true;
} else {
$sql = $this->sql;
return false;
}
}
}

View File

@ -1,76 +0,0 @@
<?php
namespace nulib\db\pdo;
use nulib\db\_private\_base;
use nulib\db\_private\Tbindings;
use nulib\ValueException;
class _query_base extends _base {
use Tbindings;
protected static function verifix(&$sql, ?array &$bindinds=null, ?array &$meta=null): void {
if (is_array($sql)) {
$prefix = $sql[0] ?? null;
if ($prefix === null) {
throw new ValueException("requête invalide");
} elseif (_query_create::isa($prefix)) {
$sql = _query_create::parse($sql, $bindinds);
$meta = ["isa" => "create", "type" => "ddl"];
} elseif (_query_select::isa($prefix)) {
$sql = _query_select::parse($sql, $bindinds);
$meta = ["isa" => "select", "type" => "dql"];
} elseif (_query_insert::isa($prefix)) {
$sql = _query_insert::parse($sql, $bindinds);
$meta = ["isa" => "insert", "type" => "dml"];
} elseif (_query_update::isa($prefix)) {
$sql = _query_update::parse($sql, $bindinds);
$meta = ["isa" => "update", "type" => "dml"];
} elseif (_query_delete::isa($prefix)) {
$sql = _query_delete::parse($sql, $bindinds);
$meta = ["isa" => "delete", "type" => "dml"];
} elseif (_query_generic::isa($prefix)) {
$sql = _query_generic::parse($sql, $bindinds);
$meta = ["isa" => "generic", "type" => null];
} else {
throw ValueException::invalid_kind($sql, "query");
}
} else {
if (!is_string($sql)) $sql = strval($sql);
if (_query_create::isa($sql)) {
$meta = ["isa" => "create", "type" => "ddl"];
} elseif (_query_select::isa($sql)) {
$meta = ["isa" => "select", "type" => "dql"];
} elseif (_query_insert::isa($sql)) {
$meta = ["isa" => "insert", "type" => "dml"];
} elseif (_query_update::isa($sql)) {
$meta = ["isa" => "update", "type" => "dml"];
} elseif (_query_delete::isa($sql)) {
$meta = ["isa" => "delete", "type" => "dml"];
} elseif (_query_generic::isa($sql)) {
$meta = ["isa" => "generic", "type" => null];
} else {
$meta = ["isa" => "generic", "type" => null];
}
}
}
const DEBUG_QUERIES = false;
function useStmt(\PDO $db, ?\PDOStatement &$stmt=null, ?string &$sql=null): bool {
if (static::DEBUG_QUERIES) { #XXX
error_log($this->sql);
//error_log(var_export($this->bindings, true));
}
if ($this->bindings !== null) {
$stmt = $db->prepare($this->sql);
foreach ($this->bindings as $name => $value) {
$this->verifixBindings($value);
$stmt->bindValue($name, $value);
}
return true;
} else {
$sql = $this->sql;
return false;
}
}
}

View File

@ -1,10 +0,0 @@
<?php
namespace nulib\db\pdo;
use nulib\db\_private\_create;
use nulib\db\_private\Tcreate;
class _query_create extends _query_base {
use Tcreate;
const SCHEMA = _create::SCHEMA;
}

View File

@ -1,10 +0,0 @@
<?php
namespace nulib\db\pdo;
use nulib\db\_private\_delete;
use nulib\db\_private\Tdelete;
class _query_delete extends _query_base {
use Tdelete;
const SCHEMA = _delete::SCHEMA;
}

View File

@ -1,10 +0,0 @@
<?php
namespace nulib\db\pdo;
use nulib\db\_private\_generic;
use nulib\db\_private\Tgeneric;
class _query_generic extends _query_base {
use Tgeneric;
const SCHEMA = _generic::SCHEMA;
}

View File

@ -1,10 +0,0 @@
<?php
namespace nulib\db\pdo;
use nulib\db\_private\_insert;
use nulib\db\_private\Tinsert;
class _query_insert extends _query_base {
use Tinsert;
const SCHEMA = _insert::SCHEMA;
}

View File

@ -1,10 +0,0 @@
<?php
namespace nulib\db\pdo;
use nulib\db\_private\_select;
use nulib\db\_private\Tselect;
class _query_select extends _query_base {
use Tselect;
const SCHEMA = _select::SCHEMA;
}

View File

@ -1,10 +0,0 @@
<?php
namespace nulib\db\pdo;
use nulib\db\_private\_update;
use nulib\db\_private\Tupdate;
class _query_update extends _query_base {
use Tupdate;
const SCHEMA = _update::SCHEMA;
}

294
php/src/db/pgsql/Pgsql.php Normal file
View File

@ -0,0 +1,294 @@
<?php
namespace nulib\db\pgsql;
use nulib\cl;
use nulib\db\_private\_config;
use nulib\db\_private\Tvalues;
use nulib\db\IDatabase;
use nulib\db\ITransactor;
use nulib\php\func;
use nulib\ValueException;
class Pgsql implements IDatabase {
use Tvalues;
static function with($pgsql, ?array $params=null): self {
if ($pgsql instanceof static) {
return $pgsql;
} elseif ($pgsql instanceof self) {
# recréer avec les mêmes paramètres
return new static(null, cl::merge([
"dbconn" => $pgsql->dbconn,
"options" => $pgsql->options,
"config" => $pgsql->config,
"migration" => $pgsql->migration,
], $params));
} else {
return new static($pgsql, $params);
}
}
protected const OPTIONS = [
"persistent" => true,
"force_new" => false,
"serial_support" => true,
];
const CONFIG = null;
const MIGRATION = null;
const params_SCHEMA = [
"dbconn" => ["array"],
"options" => ["?array|callable"],
"replace_config" => ["?array|callable"],
"config" => ["?array|callable"],
"migration" => ["?array|string|callable"],
"auto_open" => ["bool", true],
];
const dbconn_SCHEMA = [
"" => "?string",
"host" => "string",
"hostaddr" => "?string",
"port" => "?int",
"dbname" => "string",
"user" => "string",
"password" => "string",
"connect_timeout" => "?int",
"options" => "?string",
"sslmode" => "?string",
"service" => "?string",
];
protected const dbconn_MAP = [
"name" => "dbname",
"pass" => "password",
];
const options_SCHEMA = [
"persistent" => ["bool", self::OPTIONS["persistent"]],
"force_new" => ["bool", self::OPTIONS["force_new"]],
];
function __construct($dbconn=null, ?array $params=null) {
if ($dbconn !== null) {
if (!is_array($dbconn)) {
$dbconn = ["" => $dbconn];
#XXX à terme, il faudra interroger config
#$tmp = config::db($dbconn);
#if ($tmp !== null) $dbconn = $tmp;
#else $dbconn = ["" => $dbconn];
}
$params["dbconn"] = $dbconn;
}
# dbconn
$this->dbconn = $params["dbconn"] ?? null;
# options
$this->options = $params["options"] ?? static::OPTIONS;
# configuration
$config = $params["replace_config"] ?? null;
if ($config === null) {
$config = $params["config"] ?? static::CONFIG;
if (is_callable($config)) $config = [$config];
}
$this->config = $config;
# migrations
$this->migration = $params["migration"] ?? static::MIGRATION;
#
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
if ($params["auto_open"] ?? $defaultAutoOpen) {
$this->open();
}
}
protected ?array $dbconn;
/** @var array|callable|null */
protected $options;
/** @var array|string|callable */
protected $config;
/** @var array|string|callable */
protected $migration;
/** @var resource */
protected $db = null;
function open(): self {
if ($this->db === null) {
$dbconn = $this->dbconn;
$connection_string = [$dbconn[""] ?? null];
unset($dbconn[""]);
foreach ($dbconn as $key => $value) {
if ($value === null) continue;
$value = strval($value);
if ($value === "" || preg_match("/[ '\\\\]/", $value)) {
$value = str_replace("\\", "\\\\", $value);
$value = str_replace("'", "\\'", $value);
$value = "'$value'";
}
$key = cl::get(self::dbconn_MAP, $key, $key);
$connection_string[] = "$key=$value";
}
$connection_string = implode(" ", array_filter($connection_string));
$options = $this->options;
if (is_callable($options)) {
$options = func::with($options)->bind($this, true)->invoke();
}
$forceNew = $options["force_new"] ?? false;
$flags = $forceNew? PGSQL_CONNECT_FORCE_NEW: 0;
if ($options["persistent"] ?? true) $db = pg_pconnect($connection_string, $flags);
else $db = pg_connect($connection_string, $flags);
if ($db === false) throw new PgsqlException("unable to connect");
$this->db = $db;
_config::with($this->config)->configure($this);
//_migration::with($this->migration)->migrate($this);
}
return $this;
}
function close(): self {
if ($this->db !== null) {
pg_close($this->db);
$this->db = null;
}
return $this;
}
protected function db() {
$this->open();
return $this->db;
}
function _exec(string $query): bool {
$result = pg_query($this->db(), $query);
if ($result === false) return false;
pg_free_result($result);
return true;
}
function getLastSerial() {
$db = $this->db();
$result = @pg_query($db, "select lastval()");
if ($result === false) return false;
$lastSerial = pg_fetch_row($result)[0];
pg_free_result($result);
return $lastSerial;
}
function exec($query, ?array $params=null) {
$db = $this->db();
$query = new _pgsqlQuery($query, $params);
$result = $query->_exec($db);
$serialSupport = $this->options["serial_support"] ?? true;
if ($serialSupport && $query->isInsert()) return $this->getLastSerial();
$affected_rows = pg_affected_rows($result);
pg_free_result($result);
return $affected_rows;
}
/** @var ITransactor[] */
protected ?array $transactors = null;
function willUpdate(...$transactors): self {
foreach ($transactors as $transactor) {
if ($transactor instanceof ITransactor) {
$this->transactors[] = $transactor;
$transactor->willUpdate();
} else {
throw ValueException::invalid_type($transactor, ITransactor::class);
}
}
return $this;
}
function inTransaction(?bool &$inerror=null): bool {
$status = pg_transaction_status($this->db());
if ($status === PGSQL_TRANSACTION_ACTIVE || $status === PGSQL_TRANSACTION_INTRANS) {
$inerror = false;
return true;
} elseif ($status === PGSQL_TRANSACTION_INERROR) {
$inerror = true;
return true;
} else {
return false;
}
}
function beginTransaction(?callable $func=null, bool $commit=true): void {
$this->_exec("begin");
if ($this->transactors !== null) {
foreach ($this->transactors as $transactor) {
$transactor->beginTransaction();
}
}
if ($func !== null) {
$commited = false;
try {
func::call($func, $this);
if ($commit) {
$this->commit();
$commited = true;
}
} finally {
if ($commit && !$commited) $this->rollback();
}
}
}
function commit(): void {
$this->_exec("commit");
if ($this->transactors !== null) {
foreach ($this->transactors as $transactor) {
$transactor->commit();
}
}
}
function rollback(): void {
$this->_exec("rollback");
if ($this->transactors !== null) {
foreach ($this->transactors as $transactor) {
$transactor->rollback();
}
}
}
function get($query, ?array $params=null, bool $entireRow=false) {
$db = $this->db();
$query = new _pgsqlQuery($query, $params);
$result = $query->_exec($db);
$row = pg_fetch_assoc($result);
pg_free_result($result);
if ($row === false) return null;
$this->verifixRow($row);
if ($entireRow) return $row;
else return cl::first($row);
}
function one($query, ?array $params=null): ?array {
return $this->get($query, $params, true);
}
function all($query, ?array $params=null, $primaryKeys=null): iterable {
$db = $this->db();
$query = new _pgsqlQuery($query, $params);
$result = $query->_exec($db);
$primaryKeys = cl::withn($primaryKeys);
while (($row = pg_fetch_assoc($result)) !== false) {
$this->verifixRow($row);
if ($primaryKeys !== null) {
$key = implode("-", cl::select($row, $primaryKeys));
yield $key => $row;
} else {
yield $row;
}
}
pg_free_result($result);
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace nulib\db\pgsql;
use Exception;
use RuntimeException;
use SQLite3;
class PgsqlException extends RuntimeException {
static final function last_error($db): self {
return new static(pg_last_error($db));
}
static final function wrap(Exception $e): self {
return new static($e->getMessage(), $e->getCode(), $e);
}
}

View File

@ -0,0 +1,64 @@
<?php
namespace nulib\db\pgsql;
use nulib\cl;
use nulib\db\CapacitorChannel;
use nulib\db\CapacitorStorage;
class PgsqlStorage extends CapacitorStorage {
const SERDATA_DEFINITION = "text";
const SERSUM_DEFINITION = "varchar(40)";
const SERTS_DEFINITION = "timestamp";
function __construct($pgsql) {
$this->db = Pgsql::with($pgsql);
}
protected Pgsql $db;
function db(): Pgsql {
return $this->db;
}
const PRIMARY_KEY_DEFINITION = [
"id_" => "serial primary key",
];
function _getMigration(CapacitorChannel $channel): _pgsqlMigration {
return new _pgsqlMigration(cl::merge([
$this->_createSql($channel),
], $channel->getMigration()), $channel->getName());
}
function _getCreateSql(CapacitorChannel $channel): string {
$query = new _pgsqlQuery($this->_createSql($channel));
return self::format_sql($channel, $query->getSql());
}
protected function _addToChannelsSql(CapacitorChannel $channel): array {
return cl::merge(parent::_addToChannelsSql($channel), [
"suffix" => "on conflict (name) do nothing",
]);
}
function _exists(CapacitorChannel $channel): bool {
$tableName = $channel->getTableName();
if (($index = strpos($tableName, ".")) !== false) {
$schemaName = substr($tableName, 0, $index);
$tableName = substr($tableName, $index + 1);
} else {
$schemaName = "public";
}
return null !== $this->db->get([
"select tablename from pg_tables",
"where" => [
"schemaname" => $schemaName,
"tablename" => $tableName,
],
]);
}
function close(): void {
$this->db->close();
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace nulib\db\pgsql;
use nulib\db\_private\_migration;
class _pgsqlMigration extends _migration {
static function with($migration): self {
if ($migration instanceof self) return $migration;
else return new static($migration);
}
protected function setMigrated(string $name, bool $done): void {
$this->db->exec([
"insert",
"into" => static::MIGRATION_TABLE,
"values" => [
"channel" => $this->channel,
"name" => $name,
"done" => $done? 1: 0,
],
"suffix" => "on conflict (channel, name) do update set done = :done",
]);
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace nulib\db\pgsql;
use nulib\cv;
use nulib\db\_private\_base;
use nulib\db\_private\Tbindings;
use nulib\output\msg;
class _pgsqlQuery extends _base {
use Tbindings;
const DEBUG_QUERIES = false;
/**
* @return resource
*/
function _exec($db) {
$sql = $this->sql;
$bindings = $this->bindings;
if (static::DEBUG_QUERIES) {#XXX
msg::info($sql);
//msg::info(var_export($bindings, true));
}
if ($bindings !== null) {
# trier d'abord les champ par ordre de longueur, pour éviter les overlaps
$names = array_keys($bindings);
usort($names, function ($a, $b) {
return -cv::compare(strlen(strval($a)), strlen(strval($b)));
});
$bparams = [];
$number = 1;
foreach ($names as $name) {
$sql = str_replace(":$name", "\$$number", $sql);
$bparams[] = $bindings[$name];
$number++;
}
$result = pg_query_params($db, $sql, $bparams);
} else {
$result = pg_query($db, $sql);
}
if ($result === false) throw PgsqlException::last_error($db);
return $result;
}
}

View File

@ -3,10 +3,11 @@ namespace nulib\db\sqlite;
use Generator;
use nulib\cl;
use nulib\db\_private\_config;
use nulib\db\_private\Tvalues;
use nulib\db\IDatabase;
use nulib\db\ITransactor;
use nulib\php\nur_func;
use nulib\php\func;
use nulib\ValueException;
use SQLite3;
use SQLite3Result;
@ -29,7 +30,7 @@ class Sqlite implements IDatabase {
"encryption_key" => $sqlite->encryptionKey,
"allow_wal" => $sqlite->allowWal,
"config" => $sqlite->config,
"migrate" => $sqlite->migration,
"migration" => $sqlite->migration,
], $params));
} elseif (is_array($sqlite)) {
return new static(null, cl::merge($sqlite, $params));
@ -71,7 +72,7 @@ class Sqlite implements IDatabase {
const CONFIG = null;
const MIGRATE = null;
const MIGRATION = null;
const params_SCHEMA = [
"file" => ["string", ""],
@ -80,7 +81,7 @@ class Sqlite implements IDatabase {
"allow_wal" => ["?bool"],
"replace_config" => ["?array|callable"],
"config" => ["?array|callable"],
"migrate" => ["?array|string|callable"],
"migration" => ["?array|string|callable"],
"auto_open" => ["bool", true],
];
@ -108,7 +109,7 @@ class Sqlite implements IDatabase {
}
$this->config = $config;
# migrations
$this->migration = $params["migrate"] ?? static::MIGRATE;
$this->migration = $params["migration"] ?? static::MIGRATION;
#
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
$this->inTransaction = false;
@ -149,7 +150,7 @@ class Sqlite implements IDatabase {
if ($this->db === null) {
$this->db = new SQLite3($this->file, $this->flags, $this->encryptionKey);
_config::with($this->config)->configure($this);
_migration::with($this->migration)->migrate($this);
_sqliteMigration::with($this->migration)->migrate($this);
$this->inTransaction = false;
}
return $this;
@ -180,15 +181,10 @@ class Sqlite implements IDatabase {
return $this->db()->exec($query);
}
private static function is_insert(?string $sql): bool {
if ($sql === null) return false;
return preg_match('/^\s*insert\b/i', $sql);
}
function exec($query, ?array $params=null) {
$db = $this->db();
$query = new _query_base($query, $params);
if ($query->useStmt($db, $stmt, $sql)) {
$query = new _sqliteQuery($query, $params);
if ($query->_use_stmt($db, $stmt, $sql)) {
try {
$result = $stmt->execute();
if ($result === false) return false;
@ -201,7 +197,7 @@ class Sqlite implements IDatabase {
} else {
$result = $db->exec($sql);
if ($result === false) return false;
if (self::is_insert($sql)) return $db->lastInsertRowID();
if ($query->isInsert()) return $db->lastInsertRowID();
else return $db->changes();
}
}
@ -237,7 +233,7 @@ class Sqlite implements IDatabase {
if ($func !== null) {
$commited = false;
try {
nur_func::call($func, $this);
func::call($func, $this);
if ($commit) {
$this->commit();
$commited = true;
@ -274,8 +270,8 @@ class Sqlite implements IDatabase {
function get($query, ?array $params=null, bool $entireRow=false) {
$db = $this->db();
$query = new _query_base($query, $params);
if ($query->useStmt($db, $stmt, $sql)) {
$query = new _sqliteQuery($query, $params);
if ($query->_use_stmt($db, $stmt, $sql)) {
try {
$result = $this->checkResult($stmt->execute());
try {
@ -300,7 +296,7 @@ class Sqlite implements IDatabase {
}
protected function _fetchResult(SQLite3Result $result, ?SQLite3Stmt $stmt=null, $primaryKeys=null): Generator {
if ($primaryKeys !== null) $primaryKeys = cl::with($primaryKeys);
$primaryKeys = cl::withn($primaryKeys);
try {
while (($row = $result->fetchArray(SQLITE3_ASSOC)) !== false) {
$this->verifixRow($row);
@ -317,14 +313,10 @@ class Sqlite implements IDatabase {
}
}
/**
* si $primaryKeys est fourni, le résultat est indexé sur la(es) colonne(s)
* spécifiée(s)
*/
function all($query, ?array $params=null, $primaryKeys=null): iterable {
$db = $this->db();
$query = new _query_base($query, $params);
if ($query->useStmt($db, $stmt, $sql)) {
$query = new _sqliteQuery($query, $params);
if ($query->_use_stmt($db, $stmt, $sql)) {
$result = $this->checkResult($stmt->execute());
return $this->_fetchResult($result, $stmt, $primaryKeys);
} else {

View File

@ -1,6 +1,7 @@
<?php
namespace nulib\db\sqlite;
use nulib\cl;
use nulib\db\CapacitorChannel;
use nulib\db\CapacitorStorage;
@ -12,8 +13,7 @@ class SqliteStorage extends CapacitorStorage {
$this->db = Sqlite::with($sqlite);
}
/** @var Sqlite */
protected $db;
protected Sqlite $db;
function db(): Sqlite {
return $this->db;
@ -23,8 +23,14 @@ class SqliteStorage extends CapacitorStorage {
"id_" => "integer primary key autoincrement",
];
function _getMigration(CapacitorChannel $channel): _sqliteMigration {
return new _sqliteMigration(cl::merge([
$this->_createSql($channel),
], $channel->getMigration()), $channel->getName());
}
function _getCreateSql(CapacitorChannel $channel): string {
$query = new _query_base($this->_createSql($channel));
$query = new _sqliteQuery($this->_createSql($channel));
return self::format_sql($channel, $query->getSql());
}
@ -39,54 +45,33 @@ class SqliteStorage extends CapacitorStorage {
}
function channelExists(string $name): bool {
$name = $this->db->get([
"select name from _channels",
return null !== $this->db->get([
"select name",
"from" => static::CHANNELS_TABLE,
"where" => ["name" => $name],
]);
return $name !== null;
}
protected function _addToChannelsSql(CapacitorChannel $channel): array {
$sql = parent::_addToChannelsSql($channel);
$sql[0] = "insert or ignore";
return $sql;
}
protected function _afterCreate(CapacitorChannel $channel): void {
$db = $this->db;
if (!$this->tableExists("_channels")) {
if (!$this->tableExists(static::CHANNELS_TABLE)) {
# ne pas créer si la table existe déjà, pour éviter d'avoir besoin d'un
# verrou en écriture
$db->exec([
"create table if not exists",
"table" => "_channels",
"cols" => [
"name" => "varchar primary key",
"table_name" => "varchar",
"class" => "varchar",
],
]);
$db->exec($this->_createChannelsSql());
}
if (!$this->channelExists($channel->getName())) {
# ne pas insérer si la ligne existe déjà, pour éviter d'avoir besoin d'un
# verrou en écriture
$db->exec([
"insert",
"into" => "_channels",
"values" => [
"name" => $channel->getName(),
"table_name" => $channel->getTableName(),
"class" => get_class($channel),
],
"suffix" => "on conflict do nothing",
]);
$db->exec($this->_addToChannelsSql($channel));
}
}
protected function _beforeReset(CapacitorChannel $channel): void {
$this->db->exec([
"delete",
"from" => "_channels",
"where" => [
"name" => $channel->getName(),
],
]);
}
function _exists(CapacitorChannel $channel): bool {
return $this->tableExists($channel->getTableName());
}

View File

@ -1,36 +0,0 @@
<?php
namespace nulib\db\sqlite;
use nulib\php\nur_func;
class _config {
static function with($configs): self {
if ($configs instanceof static) return $configs;
return new static($configs);
}
const CONFIG = null;
function __construct($configs) {
if ($configs === null) $configs = static::CONFIG;
if ($configs === null) $configs = [];
elseif (is_string($configs)) $configs = [$configs];
elseif (is_callable($configs)) $configs = [$configs];
elseif (!is_array($configs)) $configs = [strval($configs)];
$this->configs = $configs;
}
/** @var array */
protected $configs;
function configure(Sqlite $sqlite): void {
foreach ($this->configs as $key => $config) {
if (is_string($config) && !nur_func::is_method($config)) {
$sqlite->exec($config);
} else {
nur_func::ensure_func($config, $this, $args);
nur_func::call($config, $sqlite, $key, ...$args);
}
}
}
}

View File

@ -1,55 +0,0 @@
<?php
namespace nulib\db\sqlite;
use nulib\php\nur_func;
class _migration {
static function with($migrations): self {
if ($migrations instanceof static) {
return $migrations;
} elseif ($migrations instanceof self) {
return new static($migrations->migrations);
} else {
return new static($migrations);
}
}
const MIGRATE = null;
function __construct($migrations) {
if ($migrations === null) $migrations = static::MIGRATE;
if ($migrations === null) $migrations = [];
elseif (is_string($migrations)) $migrations = [$migrations];
elseif (is_callable($migrations)) $migrations = [$migrations];
elseif (!is_array($migrations)) $migrations = [strval($migrations)];
$this->migrations = $migrations;
}
/** @var callable[]|string[] */
protected $migrations;
function migrate(Sqlite $sqlite): void {
$sqlite->exec("create table if not exists _migration(key varchar primary key, value varchar not null, done integer default 0)");
foreach ($this->migrations as $key => $migration) {
$exists = $sqlite->get("select 1 from _migration where key = :key and done = 1", [
"key" => $key,
]);
if (!$exists) {
$sqlite->exec("insert or replace into _migration(key, value, done) values(:key, :value, :done)", [
"key" => $key,
"value" => $migration,
"done" => 0,
]);
if (is_string($migration) && !nur_func::is_method($migration)) {
$sqlite->exec($migration);
} else {
nur_func::ensure_func($migration, $this, $args);
nur_func::call($migration, $sqlite, $key, ...$args);
}
$sqlite->exec("update _migration set done = 1 where key = :key", [
"key" => $key,
]);
}
}
}
}

View File

@ -1,62 +0,0 @@
<?php
namespace nulib\db\sqlite;
use nulib\db\_private\_base;
use nulib\db\_private\Tbindings;
use nulib\output\msg;
use nulib\ValueException;
use SQLite3;
use SQLite3Stmt;
class _query_base extends _base {
use Tbindings;
protected static function verifix(&$sql, ?array &$bindinds=null, ?array &$meta=null): void {
if (is_array($sql)) {
$prefix = $sql[0] ?? null;
if ($prefix === null) {
throw new ValueException("requête invalide");
} elseif (_query_create::isa($prefix)) {
$sql = _query_create::parse($sql, $bindinds);
} elseif (_query_select::isa($prefix)) {
$sql = _query_select::parse($sql, $bindinds);
} elseif (_query_insert::isa($prefix)) {
$sql = _query_insert::parse($sql, $bindinds);
} elseif (_query_update::isa($prefix)) {
$sql = _query_update::parse($sql, $bindinds);
} elseif (_query_delete::isa($prefix)) {
$sql = _query_delete::parse($sql, $bindinds);
} elseif (_query_generic::isa($prefix)) {
$sql = _query_generic::parse($sql, $bindinds);
} else {
throw SqliteException::wrap(ValueException::invalid_kind($sql, "query"));
}
} elseif (!is_string($sql)) {
$sql = strval($sql);
}
}
const DEBUG_QUERIES = false;
function useStmt(SQLite3 $db, ?SQLite3Stmt &$stmt=null, ?string &$sql=null): bool {
if (static::DEBUG_QUERIES) msg::info($this->sql); #XXX
if ($this->bindings !== null) {
/** @var SQLite3Stmt $stmt */
$stmt = SqliteException::check($db, $db->prepare($this->sql));
$close = true;
try {
foreach ($this->bindings as $param => $value) {
$this->verifixBindings($value);
SqliteException::check($db, $stmt->bindValue($param, $value));
}
$close = false;
return true;
} finally {
if ($close) $stmt->close();
}
} else {
$sql = $this->sql;
return false;
}
}
}

View File

@ -1,10 +0,0 @@
<?php
namespace nulib\db\sqlite;
use nulib\db\_private\_create;
use nulib\db\_private\Tcreate;
class _query_create extends _query_base {
use Tcreate;
const SCHEMA = _create::SCHEMA;
}

View File

@ -1,10 +0,0 @@
<?php
namespace nulib\db\sqlite;
use nulib\db\_private\_delete;
use nulib\db\_private\Tdelete;
class _query_delete extends _query_base {
use Tdelete;
const SCHEMA = _delete::SCHEMA;
}

View File

@ -1,10 +0,0 @@
<?php
namespace nulib\db\sqlite;
use nulib\db\_private\_generic;
use nulib\db\_private\Tgeneric;
class _query_generic extends _query_base {
use Tgeneric;
const SCHEMA = _generic::SCHEMA;
}

View File

@ -1,10 +0,0 @@
<?php
namespace nulib\db\sqlite;
use nulib\db\_private\_insert;
use nulib\db\_private\Tinsert;
class _query_insert extends _query_base {
use Tinsert;
const SCHEMA = _insert::SCHEMA;
}

View File

@ -1,10 +0,0 @@
<?php
namespace nulib\db\sqlite;
use nulib\db\_private\_select;
use nulib\db\_private\Tselect;
class _query_select extends _query_base {
use Tselect;
const SCHEMA = _select::SCHEMA;
}

View File

@ -1,10 +0,0 @@
<?php
namespace nulib\db\sqlite;
use nulib\db\_private\_update;
use nulib\db\_private\Tupdate;
class _query_update extends _query_base {
use Tupdate;
const SCHEMA = _update::SCHEMA;
}

View File

@ -0,0 +1,23 @@
<?php
namespace nulib\db\sqlite;
use nulib\db\_private\_migration;
class _sqliteMigration extends _migration {
static function with($migration): self {
if ($migration instanceof self) return $migration;
else return new static($migration);
}
protected function setMigrated(string $name, bool $done): void {
$this->db->exec([
"insert or replace",
"into" => static::MIGRATION_TABLE,
"values" => [
"channel" => $this->channel,
"name" => $name,
"done" => $done? 1: 0,
],
]);
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace nulib\db\sqlite;
use nulib\db\_private\_base;
use nulib\db\_private\_create;
use nulib\db\_private\_delete;
use nulib\db\_private\_generic;
use nulib\db\_private\_insert;
use nulib\db\_private\_select;
use nulib\db\_private\_update;
use nulib\db\_private\Tbindings;
use nulib\output\msg;
use nulib\ValueException;
use SQLite3;
use SQLite3Stmt;
class _sqliteQuery extends _base {
use Tbindings;
const DEBUG_QUERIES = false;
function _use_stmt(SQLite3 $db, ?SQLite3Stmt &$stmt=null, ?string &$sql=null): bool {
if (static::DEBUG_QUERIES) {#XXX
msg::info($this->sql);
//msg::info(var_export($this->bindings, true));
}
if ($this->bindings !== null) {
/** @var SQLite3Stmt $stmt */
$stmt = SqliteException::check($db, $db->prepare($this->sql));
$close = true;
try {
foreach ($this->bindings as $param => $value) {
$this->verifixBindings($value);
SqliteException::check($db, $stmt->bindValue($param, $value));
}
$close = false;
return true;
} finally {
if ($close) $stmt->close();
}
} else {
$sql = $this->sql;
return false;
}
}
}

View File

@ -5,7 +5,7 @@ use DateTimeInterface;
use nulib\cl;
use nulib\file\TempStream;
use nulib\os\path;
use nulib\php\nur_func;
use nulib\php\func;
use nulib\php\time\DateTime;
use nulib\web\http;
@ -35,13 +35,8 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
$this->rows = $rows;
$this->index = 0;
$cookFunc = $params["cook_func"] ?? null;
$cookCtx = $cookArgs = null;
if ($cookFunc !== null) {
nur_func::ensure_func($cookFunc, $this, $cookArgs);
$cookCtx = nur_func::_prepare($cookFunc);
}
$this->cookCtx = $cookCtx;
$this->cookArgs = $cookArgs;
if ($cookFunc !== null) $cookFunc = func::with($cookFunc)->bind($this, true);
$this->cookFunc = $cookFunc;
$this->output = $params["output"] ?? static::OUTPUT;
$maxMemory = $params["max_memory"] ?? null;
$throwOnError = $params["throw_on_error"] ?? null;
@ -60,9 +55,7 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
protected ?string $output;
protected ?array $cookCtx;
protected ?array $cookArgs;
protected ?func $cookFunc;
protected function ensureHeaders(?array $row=null): void {
if ($this->headers !== null || !$this->useHeaders) return;
@ -87,9 +80,8 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
}
protected function cookRow(?array $row): ?array {
if ($this->cookCtx !== null) {
$args = cl::merge([$row], $this->cookArgs);
$row = nur_func::_call($this->cookCtx, $args);
if ($this->cookFunc !== null) {
$row = $this->cookFunc->prependArgs([$row])->invoke();
}
if ($row !== null) {
foreach ($row as &$col) {

View File

@ -2,7 +2,7 @@
namespace nulib\output;
use nulib\output\std\ProxyMessenger;
use nulib\php\nur_func;
use nulib\php\func;
/**
* Class msg: inscrire un message dans les logs ET l'afficher à l'utilisateur
@ -39,30 +39,21 @@ class msg extends _messenger {
if ($log !== null && $log !== false) {
if ($log instanceof IMessenger) log::set_messenger($log);
elseif (is_string($log)) log::set_messenger_class($log);
elseif (is_array($log)) {
nur_func::ensure_class($log, $args);
$log = nur_func::cons($log, $args);
}
else $log = func::call($log);
log::set_messenger($log);
$msgs[] = $log;
}
if ($console !== null && $console !== false) {
if ($console instanceof IMessenger) console::set_messenger($console);
elseif (is_string($console)) console::set_messenger_class($console);
elseif (is_array($console)) {
nur_func::ensure_class($console, $args);
$console = nur_func::cons($console, $args);
}
else $console = func::call($console);
console::set_messenger($console);
$msgs[] = $console;
}
if ($say !== null && $say !== false) {
if ($say instanceof IMessenger) say::set_messenger($say);
elseif (is_string($say)) say::set_messenger_class($say);
elseif (is_array($say)) {
nur_func::ensure_class($say, $args);
$say = nur_func::cons($say, $args);
}
else $say = func::call($say);
say::set_messenger($say);
$msgs[] = $say;
}

View File

@ -3,7 +3,7 @@ namespace nulib\php\content;
use Closure;
use nulib\cl;
use nulib\php\nur_func;
use nulib\php\func;
/**
* Class c: classe outil pour gérer du contenu
@ -62,8 +62,7 @@ class c {
# contenu dynamique: le contenu est la valeur de retour de la fonction
# ce contenu est rajouté à la suite après avoir été quoté avec self::q()
$func = $value;
nur_func::ensure_func($func, $object_or_class, $args);
$values = self::q(nur_func::call($func, ...$args));
$values = self::q(func::call($func));
self::add_static_content($dest, $values, $key, $seq);
continue;
}
@ -83,16 +82,7 @@ class c {
$arg = self::resolve($arg, $object_or_class, false);
if (!$array) $arg = $arg[0];
}; unset($arg);
if (nur_func::is_static($func)) {
nur_func::ensure_func($func, $object_or_class, $args);
$value = nur_func::call($func, ...$args);
} elseif (nur_func::is_class($func)) {
nur_func::fix_class_args($func, $args);
$value = nur_func::cons($func, ...$args);
} else {
nur_func::ensure_func($func, $object_or_class, $args);
$value = nur_func::call($func, ...$args);
}
$value = func::with($func, $args)->bind($object_or_class, true)->invoke();
}
}
if ($seq) $dest[] = $value;

View File

@ -3,13 +3,16 @@ namespace nulib\php;
use Closure;
use Exception;
use Generator;
use nulib\A;
use nulib\cl;
use nulib\cv;
use nulib\StateException;
use nulib\ValueException;
use ReflectionClass;
use ReflectionFunction;
use ReflectionMethod;
use Traversable;
/**
* Class func: outils pour appeler fonctions et méthodes dynamiquement
@ -58,10 +61,7 @@ class func {
* la fonction (ne pas uniquement faire une vérification syntaxique)
*/
static function verifix_function(&$func, bool $strict=true, ?string &$reason=null): bool {
if ($strict) {
$msg = var_export($func, true);
$reason = null;
}
if ($strict) $reason = null;
if ($func instanceof ReflectionFunction) return true;
if (is_string($func)) {
$c = false;
@ -82,11 +82,11 @@ class func {
if ($strict) {
$reason = null;
if (class_exists($f)) {
$reason = "$msg: is a class";
$reason = "$f: is a class";
return false;
}
if (!function_exists($f)) {
$reason = "$msg: function not found";
$reason = "$f: function not found";
return false;
}
}
@ -117,10 +117,7 @@ class func {
* faire une vérification syntaxique)
*/
static function verifix_class(&$func, bool $strict=true, ?string &$reason=null): bool {
if ($strict) {
$msg = var_export($func, true);
$reason = null;
}
if ($strict) $reason = null;
if ($func instanceof ReflectionClass) return true;
if (is_string($func)) {
$c = $func;
@ -138,12 +135,10 @@ class func {
if (self::_parse_static($c)) return false;
if (self::_parse_method($c)) return false;
if ($f !== false) return false;
if ($strict) {
if (!class_exists($c)) {
$reason = "$msg: class not found";
if ($strict && !class_exists($c)) {
$reason = "$c: class not found";
return false;
}
}
$func = [$c, false];
return true;
}
@ -207,10 +202,7 @@ class func {
* la méthode est liée (ne pas uniquement faire une vérification syntaxique)
*/
static function verifix_static(&$func, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
if ($strict) {
$msg = var_export($func, true);
$reason = null;
}
if ($strict) $reason = null;
if ($func instanceof ReflectionMethod) {
$bound = false;
return true;
@ -265,18 +257,19 @@ class func {
return false;
}
if ($strict) {
[$c, $f] = $cf;
$reason = null;
if ($bound) {
if (!class_exists($c)) {
$reason = "$msg: class not found";
$reason = "$c: class not found";
return false;
}
if (!method_exists($c, $f)) {
$reason = "$msg: method not found";
$reason = "$c::$f: method not found";
return false;
}
} else {
$reason = "$msg: not bound";
$reason = "$c::$f: not bound";
}
}
$func = $cf;
@ -342,10 +335,7 @@ class func {
* la méthode est liée (ne pas uniquement faire une vérification syntaxique)
*/
static function verifix_method(&$func, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
if ($strict) {
$msg = var_export($func, true);
$reason = null;
}
if ($strict) $reason = null;
if ($func instanceof ReflectionMethod) {
$bound = false;
return true;
@ -401,18 +391,19 @@ class func {
return false;
}
if ($strict) {
[$c, $f] = $cf;
$reason = null;
if ($bound) {
if (!is_object($c) && !class_exists($c)) {
$reason = "$msg: class not found";
$reason = "$c: class not found";
return false;
}
if (!method_exists($c, $f)) {
$reason = "$msg: method not found";
$reason = "$c::$f: method not found";
return false;
}
} else {
$reason = "$msg: not bound";
$reason = "$c::$f: not bound";
}
}
$func = $cf;
@ -446,7 +437,7 @@ class func {
return new ValueException($reason);
}
static function with($func, ?array $args=null, bool $strict=true): self {
private static function _with($func, ?array $args=null, bool $strict=true, ?string &$reason=null): ?self {
if (!is_array($func)) {
if ($func instanceof Closure) {
return new self(self::TYPE_CLOSURE, $func, $args);
@ -467,6 +458,12 @@ class func {
} elseif (self::verifix_static($func, $strict, $bound, $reason)) {
return new self(self::TYPE_STATIC, $func, $args, $bound, $reason);
}
return null;
}
static function with($func, ?array $args=null, bool $strict=true): self {
$func = self::_with($func, $args, $strict, $reason);
if ($func !== null) return $func;
throw self::not_a_callable($func, $reason);
}
@ -487,10 +484,45 @@ class func {
}
}
static function is_callable($func): bool {
$func = self::_with($func);
if ($func === null) return false;
if (!$func->isBound()) return false;
return $func->type !== self::TYPE_CLASS;
}
static function call($func, ...$args) {
return self::with($func)->invoke($args);
}
/**
* si $value est une fonction, l'appeler
* si $value ou le résultat de l'appel est un Traversable, le résoudre
* sinon retourner $value tel quel
*
* en définitive, la valeur de retour de cette fonction est soit un scalaire,
* soit un array, soit un objet qui n'est pas Traversable
* @return mixed
*/
static function get_value($value, ...$args) {
if ($value instanceof self) $value = $value->invoke($args);
elseif (is_callable($value)) $value = self::call($value, ...$args);
if ($value instanceof Traversable) $value = cl::all($value);
return $value;
}
/**
* si $value est une fonction, l'appeler
* si $value ou le résultat de l'appel est un Traversable, le retourner
* sinon retourner $value en tant qu'array
*/
static function get_iterable($value, ...$args): ?iterable {
if ($value instanceof self) $value = $value->invoke($args);
elseif (is_callable($value)) $value = self::call($value, ...$args);
if ($value instanceof Traversable) return $value;
else return cl::withn($value);
}
#############################################################################
protected function __construct(int $type, $func, ?array $args=null, bool $bound=false, ?string $reason=null) {
@ -561,6 +593,27 @@ class func {
protected int $maxArgs;
function replaceArgs(?array $args): self {
$this->prefixArgs = $args?? [];
return $this;
}
function prependArgs(?array $args, ?int $stripCount=null): self {
if ($stripCount !== null || $args !== null) {
array_splice($this->prefixArgs, 0, $stripCount ?? 0, $args);
}
return $this;
}
function appendArgs(?array $args, ?int $stripCount=null): self {
if ($stripCount !== null || $args !== null) {
$stripCount ??= 0;
if ($stripCount > 0) array_splice($this->prefixArgs, -$stripCount);
$this->prefixArgs = array_merge($this->prefixArgs, $args);
}
return $this;
}
protected function updateReflection($reflection): void {
$variadic = false;
$minArgs = $maxArgs = 0;
@ -596,11 +649,16 @@ class func {
else return $this->bound && $this->object !== null;
}
function bind($object): self {
function bind($object, bool $unlessAlreadyBound=false, bool $replace=false): self {
if ($this->type !== self::TYPE_METHOD) return $this;
if ($this->bound && $unlessAlreadyBound) return $this;
[$c, $f] = $this->func;
if ($this->reflection === null) {
if ($replace) {
$c = $object;
$this->func = [$c, $f];
$this->updateReflection(new ReflectionMethod($c, $f));
} elseif ($this->reflection === null) {
$this->func[0] = $c = $object;
$this->updateReflection(new ReflectionMethod($c, $f));
}

View File

@ -44,7 +44,7 @@ class mprop {
} catch (ReflectionException $e) {
return oprop::get($object, $property, $default);
}
return nur_func::call([$object, $m], $default);
return func::call([$object, $m], $default);
}
/** spécifier la valeur d'une propriété */
@ -60,7 +60,7 @@ class mprop {
} catch (ReflectionException $e) {
return oprop::_set($c, $object, $property, $value);
}
nur_func::call([$object, $m], $value);
func::call([$object, $m], $value);
return $value;
}

View File

@ -1,453 +0,0 @@
<?php
namespace nulib\php;
use Closure;
use nulib\cl;
use nulib\ref\php\ref_func;
use nulib\ValueException;
use ReflectionClass;
use ReflectionFunction;
use ReflectionMethod;
/**
* Class func: outils pour appeler des fonctions et méthodes dynamiquement
*/
class nur_func {
/**
* tester si $func est une chaine de la forme "XXX::method" XXX est une
* chaine quelconque éventuellement vide, ou un tableau de la forme ["method"]
* ou [anything, "method", ...]
*
* Avec la forme tableau, "method" ne doit pas contenir le caractère '\', pour
* pouvoir utiliser conjointement {@link is_class()}
*/
static final function is_static($func, bool $allowClass=false): bool {
if (is_string($func)) {
$pos = strpos($func, "::");
if ($pos === false) return false;
return $pos + 2 < strlen($func);
} elseif (is_array($func) && array_key_exists(0, $func)) {
$count = count($func);
if ($count == 1) {
if (!is_string($func[0]) || strlen($func[0]) == 0) return false;
if (strpos($func[0], "\\") !== false) return false;
return true;
} elseif ($count > 1) {
if (!array_key_exists(1, $func)) return false;
if (!is_string($func[1]) || strlen($func[1]) == 0) return false;
if (strpos($func[1], "\\") !== false) return false;
return true;
}
}
return false;
}
/**
* si $func est une chaine de la forme "::method" alors la remplacer par la
* chaine "$class::method"
*
* si $func est un tableau de la forme ["method"] ou [null, "method"], alors
* le remplacer par [$class, "method"]
*
* on assume que {@link is_static()}($func) retourne true
*
* @return bool true si la correction a été faite
*/
static final function fix_static(&$func, $class): bool {
if (is_object($class)) $class = get_class($class);
if (is_string($func) && substr($func, 0, 2) == "::") {
$func = "$class$func";
return true;
} elseif (is_array($func) && array_key_exists(0, $func)) {
$count = count($func);
if ($count == 1) {
$func = [$class, $func[0]];
return true;
} elseif ($count > 1 && $func[0] === null) {
$func[0] = $class;
return true;
}
}
return false;
}
/** tester si $method est une chaine de la forme "->method" */
private static function isam($method): bool {
return is_string($method)
&& strlen($method) > 2
&& substr($method, 0, 2) == "->";
}
/**
* tester si $func est une chaine de la forme "->method" ou un tableau de la
* forme ["->method", ...] ou [anything, "->method", ...]
*/
static final function is_method($func): bool {
if (is_string($func)) {
return self::isam($func);
} elseif (is_array($func) && array_key_exists(0, $func)) {
if (self::isam($func[0])) {
# ["->method", ...]
return true;
}
if (array_key_exists(1, $func) && self::isam($func[1])) {
# [anything, "->method", ...]
return true;
}
}
return false;
}
/**
* si $func est une chaine de la forme "->method" alors la remplacer par le
* tableau [$object, "method"]
*
* si $func est un tableau de la forme ["->method"] ou [anything, "->method"],
* alors le remplacer par [$object, "method"]
*
* @return bool true si la correction a été faite
*/
static final function fix_method(&$func, $object): bool {
if (!is_object($object)) return false;
if (is_string($func)) {
if (self::isam($func)) {
$func = [$object, substr($func, 2)];
return true;
}
} elseif (is_array($func) && array_key_exists(0, $func)) {
if (self::isam($func[0])) $func = array_merge([null], $func);
if (count($func) > 1 && array_key_exists(1, $func) && self::isam($func[1])) {
$func[0] = $object;
$func[1] = substr($func[1], 2);
return true;
}
}
return false;
}
/**
* si $func est un tableau de plus de 2 éléments, alors déplacer les éléments
* supplémentaires au début de $args. par exemple:
* ~~~
* $func = ["class", "method", "arg1", "arg2"];
* $args = ["arg3"];
* func::fix_args($func, $args)
* # $func === ["class", "method"]
* # $args === ["arg1", "arg2", "arg3"]
* ~~~
*
* @return bool true si la correction a été faite
*/
static final function fix_args(&$func, ?array &$args): bool {
if ($args === null) $args = [];
if (is_array($func) && count($func) > 2) {
$prefix_args = array_slice($func, 2);
$func = array_slice($func, 0, 2);
$args = array_merge($prefix_args, $args);
return true;
}
return false;
}
/**
* s'assurer que $func est un appel de méthode ou d'une méthode statique;
* et renseigner le cas échéant les arguments. si $func ne fait pas mention
* de la classe ou de l'objet, le renseigner avec $class_or_object.
*
* @return bool true si c'est une fonction valide. il ne reste plus qu'à
* l'appeler avec {@link call()}
*/
static final function check_func(&$func, $class_or_object, &$args=null): bool {
if ($func instanceof Closure) return true;
if (self::is_method($func)) {
# méthode
self::fix_method($func, $class_or_object);
self::fix_args($func, $args);
return true;
} elseif (self::is_static($func)) {
# méthode statique
self::fix_static($func, $class_or_object);
self::fix_args($func, $args);
return true;
}
return false;
}
/**
* Comme {@link check_func()} mais lance une exception si la fonction est
* invalide
*
* @throws ValueException si $func n'est pas une fonction ou une méthode valide
*/
static final function ensure_func(&$func, $class_or_object, &$args=null): void {
if (!self::check_func($func, $class_or_object, $args)) {
throw ValueException::invalid_type($func, "callable");
}
}
static final function _prepare($func): array {
$object = null;
if (is_callable($func)) {
if (is_array($func)) {
$rf = new ReflectionMethod(...$func);
$object = $func[0];
if (is_string($object)) $object = null;
} elseif ($func instanceof Closure) {
$rf = new ReflectionFunction($func);
} elseif (is_string($func) && strpos($func, "::") === false) {
$rf = new ReflectionFunction($func);
} else {
$rf = new ReflectionMethod($func);
}
} elseif ($func instanceof ReflectionMethod) {
$rf = $func;
} elseif ($func instanceof ReflectionFunction) {
$rf = $func;
} elseif (is_array($func) && count($func) == 2 && isset($func[0]) && isset($func[1])
&& ($func[1] instanceof ReflectionMethod || $func[1] instanceof ReflectionFunction)) {
$object = $func[0];
if (is_string($object)) $object = null;
$rf = $func[1];
} elseif (is_string($func) && strpos($func, "::") === false) {
$rf = new ReflectionFunction($func);
} else {
throw ValueException::invalid_type($func, "callable");
}
$minArgs = $rf->getNumberOfRequiredParameters();
$maxArgs = $rf->getNumberOfParameters();
$variadic = $rf->isVariadic();
return [$rf instanceof ReflectionMethod, $object, $rf, $minArgs, $maxArgs, $variadic];
}
static final function _fill(array $context, array &$args): void {
$minArgs = $context[3];
$maxArgs = $context[4];
$variadic = $context[5];
if (!$variadic) $args = array_slice($args, 0, $maxArgs);
while (count($args) < $minArgs) $args[] = null;
}
static final function _call($context, array $args) {
self::_fill($context, $args);
$use_object = $context[0];
$object = $context[1];
$method = $context[2];
if ($use_object) {
if (count($args) === 0) return $method->invoke($object);
else return $method->invokeArgs($object, $args);
} else {
if (count($args) === 0) return $method->invoke();
else return $method->invokeArgs($args);
}
}
/**
* Appeler la fonction spécifiée avec les arguments spécifiés.
* Adapter $args en fonction du nombre réel d'arguments de $func
*
* @param callable|ReflectionFunction|ReflectionMethod $func
*/
static final function call($func, ...$args) {
return self::_call(self::_prepare($func), $args);
}
/** remplacer $value par $func($value, ...$args) */
static final function apply(&$value, $func, ...$args): void {
if ($func !== null) {
if ($args) $args = array_merge([$value], $args);
else $args = [$value];
$value = self::call($func, ...$args);
}
}
const MASK_PS = ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_STATIC;
const MASK_P = ReflectionMethod::IS_PUBLIC;
const METHOD_PS = ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_STATIC;
const METHOD_P = ReflectionMethod::IS_PUBLIC;
private static function matches(string $name, array $includes, array $excludes): bool {
if ($includes) {
$matches = false;
foreach ($includes as $include) {
if (substr($include, 0, 1) == "/") {
# expression régulière
if (preg_match($include, $name)) {
$matches = true;
break;
}
} else {
# tester la présence de la sous-chaine
if (strpos($name, $include) !== false) {
$matches = true;
break;
}
}
}
if (!$matches) return false;
}
foreach ($excludes as $exclude) {
if (substr($exclude, 0, 1) == "/") {
# expression régulière
if (preg_match($exclude, $name)) return false;
} else {
# tester la présence de la sous-chaine
if (strpos($name, $exclude) !== false) return false;
}
}
return true;
}
/** @var Schema */
private static $call_all_params_schema;
/**
* retourner la liste des méthodes de $class_or_object qui correspondent au
* filtre $options. le filtre doit respecter le schéme {@link CALL_ALL_PARAMS_SCHEMA}
*/
static function get_all($class_or_object, $params=null): array {
Schema::nv($paramsv, $params, null
, self::$call_all_params_schema, ref_func::CALL_ALL_PARAMS_SCHEMA);
if (is_callable($class_or_object, true) && is_array($class_or_object)) {
# callable sous forme de tableau
$class_or_object = $class_or_object[0];
}
if (is_string($class_or_object)) {
# lister les méthodes publiques statiques de la classe
$mask = self::MASK_PS;
$expected = self::METHOD_PS;
$c = new ReflectionClass($class_or_object);
} elseif (is_object($class_or_object)) {
# lister les méthodes publiques de la classe
$c = new ReflectionClass($class_or_object);
$mask = $params["static_only"]? self::MASK_PS: self::MASK_P;
$expected = $params["static_only"]? self::METHOD_PS: self::METHOD_P;
} else {
throw new ValueException("$class_or_object: vous devez spécifier une classe ou un objet");
}
$prefix = $params["prefix"]; $prefixlen = strlen($prefix);
$args = $params["args"];
$includes = $params["include"];
$excludes = $params["exclude"];
$methods = [];
foreach ($c->getMethods() as $m) {
if (($m->getModifiers() & $mask) != $expected) continue;
$name = $m->getName();
if (substr($name, 0, $prefixlen) != $prefix) continue;
if (!self::matches($name, $includes, $excludes)) continue;
$methods[] = cl::merge([$class_or_object, $name], $args);
}
return $methods;
}
/**
* Appeler toutes les méthodes publiques de $object_or_class et retourner un
* tableau [$method_name => $return_value] des valeurs de retour.
*/
static final function call_all($class_or_object, $params=null): array {
$methods = self::get_all($class_or_object, $params);
$values = [];
foreach ($methods as $method) {
self::fix_args($method, $args);
$values[$method[1]] = self::call($method, ...$args);
}
return $values;
}
/**
* tester si $func est une chaine de la forme "XXX" XXX est une classe
* valide, ou un tableau de la forme ["XXX", ...]
*
* NB: il est possible d'avoir {@link is_static()} et {@link is_class()}
* vraies pour la même valeur. s'il faut supporter les deux cas, appeler
* {@link is_static()} d'abord, mais dans ce cas, on ne supporte que les
* classes qui sont dans un package
*/
static final function is_class($class): bool {
if (is_string($class)) {
return class_exists($class);
} elseif (is_array($class) && array_key_exists(0, $class)) {
return class_exists($class[0]);
}
return false;
}
/**
* en assumant que {@link is_class()} est vrai, si $class est un tableau de
* plus de 1 éléments, alors déplacer les éléments supplémentaires au début de
* $args. par exemple:
* ~~~
* $class = ["class", "arg1", "arg2"];
* $args = ["arg3"];
* func::fix_class_args($class, $args)
* # $class === "class"
* # $args === ["arg1", "arg2", "arg3"]
* ~~~
*
* @return bool true si la correction a été faite
*/
static final function fix_class_args(&$class, ?array &$args): bool {
if ($args === null) $args = [];
if (is_array($class)) {
if (count($class) > 1) {
$prefix_args = array_slice($class, 1);
$class = array_slice($class, 0, 1)[0];
$args = array_merge($prefix_args, $args);
} else {
$class = $class[0];
}
return true;
}
return false;
}
/**
* s'assurer que $class est une classe et renseigner le cas échéant les
* arguments.
*
* @return bool true si c'est une classe valide. il ne reste plus qu'à
* l'instancier avec {@link cons()}
*/
static final function check_class(&$class, &$args=null): bool {
if (self::is_class($class)) {
self::fix_class_args($class, $args);
return true;
}
return false;
}
/**
* Comme {@link check_class()} mais lance une exception si la classe est
* invalide
*
* @throws ValueException si $class n'est pas une classe valide
*/
static final function ensure_class(&$class, &$args=null): void {
if (!self::check_class($class, $args)) {
throw ValueException::invalid_type($class, "class");
}
}
/**
* Instancier la classe avec les arguments spécifiés.
* Adapter $args en fonction du nombre réel d'arguments du constructeur
*/
static final function cons(string $class, ...$args) {
$c = new ReflectionClass($class);
$rf = $c->getConstructor();
if ($rf === null) {
return $c->newInstance();
} else {
if (!$rf->isVariadic()) {
$minArgs = $rf->getNumberOfRequiredParameters();
$maxArgs = $rf->getNumberOfParameters();
$args = array_slice($args, 0, $maxArgs);
while (count($args) < $minArgs) {
$args[] = null;
}
}
return $c->newInstanceArgs($args);
}
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace nulib\ref\schema;
class ref_input {
const ACCESS_AUTO = 0, ACCESS_KEY = 1, ACCESS_PROPERTY = 2;
const INPUT_PARAMS_SCHEMA = [
"access_type" => ["int", self::ACCESS_AUTO, "type d'accès: clé ou propriété"],
"allow_empty" => ["bool", true, "la chaine vide est-elle autorisée?"],
"allow_null" => ["bool", true, "la valeur null est-elle autorisée?"],
];
const ACCESS_PARAMS_SCHEMA = [
"allow_empty" => ["bool", true, "la chaine vide est-elle autorisée?"],
"allow_null" => ["bool", null, "la valeur null est-elle autorisée?"],
"allow_false" => ["bool", null, "la valeur false est-elle autorisée?"],
"protect_dest" => ["bool", null, "faut-il protéger la destination?"],
];
const VALUE_ACCESS_PARAMS_SCHEMA = [
"allow_null" => ["bool", false],
"allow_false" => ["bool", true],
"protect_dest" => ["bool", false],
];
const ARRAY_ACCESS_PARAMS_SCHEMA = [
"allow_null" => ["bool", true],
"allow_false" => ["bool", false],
"protect_dest" => ["bool", true],
"key_prefix" => ["?string", null, "préfixe des clés pour les méthodes ensureXxx()"],
"key_suffix" => ["?string", null, "suffixe des clés pour les méthodes ensureXxx()"],
];
const PROPERTY_ACCESS_PARAMS_SCHEMA = [
"allow_null" => ["bool", true],
"allow_false" => ["bool", false],
"protect_dest" => ["bool", true],
"key_prefix" => ["?string", null, "préfixe des clés pour les méthodes ensureXxx()"],
"key_suffix" => ["?string", null, "suffixe des clés pour les méthodes ensureXxx()"],
"map_names" => ["bool", true, "faut-il mapper les clés en camelCase?"]
];
}

View File

@ -26,6 +26,8 @@ class ref_schema {
"messages" => ["?array", null, "messages à afficher en cas d'erreur d'analyse"],
"formatter_func" => ["?callable", null, "fonction qui formatte la valeur pour affichage"],
"format" => [null, null, "format à utiliser pour l'affichage"],
"size" => ["?int", null, "nom de caractères ou de chiffres de la valeur"],
"precision" => ["?int", null, "nombre de chiffres après la virgule pour une valeur numérique flottante"],
"" => ["array", ["scalar"], "nature du schéma",
"schema" => self::NATURE_METASCHEMA,
],
@ -37,25 +39,48 @@ class ref_schema {
];
const MESSAGES = [
"missing" => "Vous devez spécifier cette valeur",
"unavailable" => "Vous devez spécifier cette valeur",
"null" => "Cette valeur ne doit pas être nulle",
"empty" => "Cette valeur ne doit pas être vide",
"invalid" => "Cette valeur est invalide",
"missing" => "vous devez spécifier cette valeur",
"unavailable" => "vous devez spécifier cette valeur",
"null" => "cette valeur ne doit pas être nulle",
"empty" => "cette valeur ne doit pas être vide",
"invalid" => "cette valeur est invalide",
];
const PARAMS_SCHEMA = [
"analyze" => ["bool", true, "faut-il analyser la valeur?"],
"reanalyze" => ["bool", true, "faut-il forcer l'analyse de la valeur?"],
"normalize" => ["bool", true, "faut-il normaliser la valeur?"],
"renormalize" => ["bool", true, "faut-il forcer la normalisation de la valeur?"],
"throw" => ["bool", true, "faut-il lancer une exception en cas d'erreur?"],
//...ref_input::INPUT_PARAMS_SCHEMA,
];
/** @var array clés supplémentaires de schéma de la nature scalaire */
const SCALAR_NATURE_METASCHEMA = [
];
const SCALAR_PARAMS_SCHEMA = [
];
/** @var array clés supplémentaires de schéma de la nature associative */
const ASSOC_NATURE_METASCHEMA = [
"ensure_array" => ["bool", false, "faut-il s'assurer que le tableau destination est non nul?"],
"ensure_keys" => ["bool", true, "faut-il s'assurer que toutes les clés existent?"],
"ensure_order" => ["bool", true, "faut-il s'assurer que les clés soient dans l'ordre?"],
"ensure_array" => ["bool", null, "faut-il s'assurer que le tableau destination est non nul?"],
"ensure_assoc" => ["bool", null, "faut-il s'assurer que le tableau destination est associatif?"],
"ensure_keys" => ["bool", null, "faut-il s'assurer que toutes les clés existent avec la valeur par défaut?"],
"ensure_order" => ["bool", null, "faut-il s'assurer que les clés soient dans l'ordre?"],
];
const ASSOC_PARAMS_SCHEMA = [
"ensure_array" => ["bool", false],
"ensure_assoc" => ["bool", true],
"ensure_keys" => ["bool", true],
"ensure_order" => ["bool", true],
];
/** @var array clés supplémentaires de schéma de la nature liste */
const LIST_NATURE_METASCHEMA = [
];
const LIST_PARAMS_SCHEMA = [
];
}

View File

@ -242,6 +242,21 @@ class str {
return true;
}
/**
* vérifier si $s a le préfixe $prefix
* - si $prefix commence par /, c'est une expression régulière, et elle doit
* matcher $s
* - sinon $s doit commencer par la chaine $prefix
*/
static final function match_prefix(?string $s, ?string $prefix): bool {
if ($s === null || $prefix === null) return false;
if (substr($prefix, 0, 1) === "/") {
return preg_match($prefix, $s);
} else {
return self::_starts_with($prefix, $s);
}
}
/**
* ajouter $sep$prefix$text$suffix à $s si $text est non vide
*

View File

@ -78,7 +78,7 @@ class ComposerFile {
];
function selectProfile(string $profile, ComposerPmanFile $config): void {
$config = $config->getProfileConfig($profile);
$config = $config->getProfileConfig($profile, $this->getRequires(), $this->getRequireDevs());
// corriger les liens
$deps = cl::merge(array_keys($config["require"]), array_keys($config["require-dev"]));
$paths = [];

View File

@ -4,6 +4,7 @@ namespace nulib\tools\pman;
use nulib\A;
use nulib\ext\yaml;
use nulib\os\path;
use nulib\str;
use nulib\ValueException;
class ComposerPmanFile {
@ -49,6 +50,8 @@ class ComposerPmanFile {
$composer =& $data["composer"];
A::ensure_array($composer);
A::ensure_array($composer["profiles"]);
A::ensure_array($composer["match_require"]);
A::ensure_array($composer["match_require-dev"]);
foreach ($composer["profiles"] as $profileName) {
$profile =& $composer[$profileName];
A::ensure_array($profile);
@ -61,11 +64,43 @@ class ComposerPmanFile {
return $this->data;
}
function getProfileConfig(string $profile): array {
function getProfileConfig(string $profile, ?array $composerRequires=null, ?array $composerRequireDevs=null): array {
$config = $this->data["composer"][$profile] ?? null;
if ($config === null) {
throw new ValueException("$profile: profil invalide");
}
if ($composerRequires !== null) {
$matchRequires = $this->data["composer"]["match_require"];
foreach ($composerRequires as $dep => $version) {
$found = false;
foreach ($matchRequires as $matchRequire) {
if (str::match_prefix($dep, $matchRequire)) {
$found = true;
break;
}
}
$require = $config["require"][$dep] ?? null;
if ($found && $require === null) {
$config["require"][$dep] = $version;
}
}
}
if ($composerRequireDevs !== null) {
$matchRequireDevs = $this->data["composer"]["match_require-dev"];
foreach ($composerRequireDevs as $dep => $version) {
$found = false;
foreach ($matchRequireDevs as $matchRequireDev) {
if (str::match_prefix($dep, $matchRequireDev)) {
$found = true;
break;
}
}
$requireDev = $config["require-dev"][$dep] ?? null;
if ($found && $requireDev === null) {
$config["require"][$dep] = $version;
}
}
}
return $config;
}

View File

@ -1,132 +0,0 @@
<?php
namespace nulib {
use nulib\tests\TestCase;
use nulib\impl\config;
use nulib\impl\myapp;
use nulib\impl\MyApplication1;
use nulib\impl\MyApplication2;
class appTest extends TestCase {
function testWith() {
$projdir = config::get_projdir();
$cwd = getcwd();
myapp::reset();
$app1 = myapp::with(MyApplication1::class);
self::assertSame([
"projdir" => $projdir,
"vendor" => [
"bindir" => "$projdir/vendor/bin",
"autoload" => "$projdir/vendor/autoload.php",
],
"appcode" => "nur-sery",
"cwd" => $cwd,
"datadir" => "$projdir/devel",
"etcdir" => "$projdir/devel/etc",
"vardir" => "$projdir/devel/var",
"logdir" => "$projdir/devel/log",
"profile" => "devel",
"appgroup" => null,
"name" => "my-application1",
"title" => null,
], $app1->getParams());
$app2 = myapp::with(MyApplication2::class, $app1);
self::assertSame([
"projdir" => $projdir,
"vendor" => [
"bindir" => "$projdir/vendor/bin",
"autoload" => "$projdir/vendor/autoload.php",
],
"appcode" => "nur-sery",
"cwd" => $cwd,
"datadir" => "$projdir/devel",
"etcdir" => "$projdir/devel/etc",
"vardir" => "$projdir/devel/var",
"logdir" => "$projdir/devel/log",
"profile" => "devel",
"appgroup" => null,
"name" => "my-application2",
"title" => null,
], $app2->getParams());
}
function testInit() {
$projdir = config::get_projdir();
$cwd = getcwd();
myapp::reset();
myapp::init(MyApplication1::class);
self::assertSame([
"projdir" => $projdir,
"vendor" => [
"bindir" => "$projdir/vendor/bin",
"autoload" => "$projdir/vendor/autoload.php",
],
"appcode" => "nur-sery",
"cwd" => $cwd,
"datadir" => "$projdir/devel",
"etcdir" => "$projdir/devel/etc",
"vardir" => "$projdir/devel/var",
"logdir" => "$projdir/devel/log",
"profile" => "devel",
"appgroup" => null,
"name" => "my-application1",
"title" => null,
], myapp::get()->getParams());
myapp::init(MyApplication2::class);
self::assertSame([
"projdir" => $projdir,
"vendor" => [
"bindir" => "$projdir/vendor/bin",
"autoload" => "$projdir/vendor/autoload.php",
],
"appcode" => "nur-sery",
"cwd" => $cwd,
"datadir" => "$projdir/devel",
"etcdir" => "$projdir/devel/etc",
"vardir" => "$projdir/devel/var",
"logdir" => "$projdir/devel/log",
"profile" => "devel",
"appgroup" => null,
"name" => "my-application2",
"title" => null,
], myapp::get()->getParams());
}
}
}
namespace nulib\impl {
use nulib\app\cli\Application;
use nulib\os\path;
use nulib\app;
class config {
const PROJDIR = __DIR__.'/..';
static function get_projdir(): string {
return path::abspath(self::PROJDIR);
}
}
class myapp extends app {
static function reset(): void {
self::$app = null;
}
}
class MyApplication1 extends Application {
const PROJDIR = config::PROJDIR;
function main() {
}
}
class MyApplication2 extends Application {
const PROJDIR = null;
function main() {
}
}
}

View File

@ -118,7 +118,7 @@ class SqliteStorageTest extends TestCase {
const COLUMN_DEFINITIONS = [
"a__" => "varchar",
"b__" => "varchar",
"b__sum_" => self::SUM_DEFINITION,
"b__sum_" => "sersum",
];
function getItemValues($item): ?array {

View File

@ -11,7 +11,7 @@ class SqliteTest extends TestCase {
function testMigration() {
$sqlite = new Sqlite(":memory:", [
"migrate" => [
"migration" => [
self::CREATE_PERSON,
self::INSERT_JEPHTE,
],
@ -49,7 +49,7 @@ class SqliteTest extends TestCase {
}
function testInsert() {
$sqlite = new Sqlite(":memory:", [
"migrate" => "create table mapping (i integer, s varchar)",
"migration" => "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"]]);
@ -78,7 +78,7 @@ class SqliteTest extends TestCase {
function testSelect() {
$sqlite = new Sqlite(":memory:", [
"migrate" => "create table user (name varchar, amount integer)",
"migration" => "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]]);
@ -130,7 +130,7 @@ class SqliteTest extends TestCase {
function testSelectGroupBy() {
$sqlite = new Sqlite(":memory:", [
"migrate" => "create table user (name varchar, amount integer)",
"migration" => "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]]);

View File

@ -6,119 +6,119 @@ use PHPUnit\Framework\TestCase;
class _queryTest extends TestCase {
function testParseConds(): void {
$sql = $params = null;
_query_base::parse_conds(null, $sql, $params);
_sqliteQuery::parse_conds(null, $sql, $params);
self::assertNull($sql);
self::assertNull($params);
$sql = $params = null;
_query_base::parse_conds([], $sql, $params);
_sqliteQuery::parse_conds([], $sql, $params);
self::assertNull($sql);
self::assertNull($params);
$sql = $params = null;
_query_base::parse_conds(["col" => null], $sql, $params);
_sqliteQuery::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);
_sqliteQuery::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);
_sqliteQuery::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);
_sqliteQuery::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);
_sqliteQuery::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);
_sqliteQuery::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);
_sqliteQuery::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);
_sqliteQuery::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);
_sqliteQuery::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);
_sqliteQuery::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);
_sqliteQuery::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);
_sqliteQuery::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);
_sqliteQuery::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);
_sqliteQuery::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);
_sqliteQuery::parse_set_values(null, $sql, $params);
self::assertNull($sql);
self::assertNull($params);
$sql = $params = null;
_query_base::parse_set_values([], $sql, $params);
_sqliteQuery::parse_set_values([], $sql, $params);
self::assertNull($sql);
self::assertNull($params);
$sql = $params = null;
_query_base::parse_set_values(["col = 'value'"], $sql, $params);
_sqliteQuery::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);
_sqliteQuery::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);
_sqliteQuery::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);
_sqliteQuery::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);
_sqliteQuery::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

@ -1103,11 +1103,34 @@ namespace nulib\php {
function testRebind() {
$func = func::with([C1::class, "tmethod"]);
// objets du même type
self::assertSame(11, $func->bind(new C1(0))->invoke());
self::assertSame(12, $func->bind(new C1(1))->invoke());
// objets de type différent
self::assertException(ValueException::class, function() use ($func) {
$func->bind(new C0())->invoke();
});
self::assertSame(11, $func->bind(new C0(), false, true)->invoke());
}
function testModifyArgs() {
$closure = function(...$args) { return $args; };
self::assertSame(["x", "y", "z"], func::with($closure, ["a", "b", "c"])->replaceArgs(["x", "y", "z"])->invoke());
self::assertSame(["x", "y", "z", "a", "b", "c"], func::with($closure, ["a", "b", "c"])->prependArgs(["x", "y", "z"])->invoke());
self::assertSame(["x", "y", "z", "a", "b", "c"], func::with($closure, ["a", "b", "c"])->prependArgs(["x", "y", "z"], 0)->invoke());
self::assertSame(["x", "y", "z", "b", "c"], func::with($closure, ["a", "b", "c"])->prependArgs(["x", "y", "z"], 1)->invoke());
self::assertSame(["x", "y", "z", "c"], func::with($closure, ["a", "b", "c"])->prependArgs(["x", "y", "z"], 2)->invoke());
self::assertSame(["x", "y", "z"], func::with($closure, ["a", "b", "c"])->prependArgs(["x", "y", "z"], 3)->invoke());
self::assertSame(["x", "y", "z"], func::with($closure, ["a", "b", "c"])->prependArgs(["x", "y", "z"], 4)->invoke());
self::assertSame(["a", "b", "c", "x", "y", "z"], func::with($closure, ["a", "b", "c"])->appendArgs(["x", "y", "z"])->invoke());
self::assertSame(["a", "b", "c", "x", "y", "z"], func::with($closure, ["a", "b", "c"])->appendArgs(["x", "y", "z"], 0)->invoke());
self::assertSame(["a", "b", "x", "y", "z"], func::with($closure, ["a", "b", "c"])->appendArgs(["x", "y", "z"], 1)->invoke());
self::assertSame(["a", "x", "y", "z"], func::with($closure, ["a", "b", "c"])->appendArgs(["x", "y", "z"], 2)->invoke());
self::assertSame(["x", "y", "z"], func::with($closure, ["a", "b", "c"])->appendArgs(["x", "y", "z"], 3)->invoke());
self::assertSame(["x", "y", "z"], func::with($closure, ["a", "b", "c"])->appendArgs(["x", "y", "z"], 4)->invoke());
}
}
}

View File

@ -1,292 +0,0 @@
<?php
namespace {
function func36(): int { return 36; }
function func_m1($a): array { return [$a]; }
function func_o1($b=9): array { return [$b]; }
function func_v(...$c): array { return [...$c]; }
function func_m1o1($a, $b=9): array { return [$a, $b]; }
function func_m1v($a, ...$c): array { return [$a, ...$c]; }
function func_m1o1v($a, $b=9, ...$c): array { return [$a, $b, ...$c]; }
function func_o1v($b=9, ...$c): array { return [$b, ...$c]; }
}
namespace nulib\php {
use nulib\tests\TestCase;
class nur_funcTest extends TestCase {
function testIs_static() {
self::assertFalse(nur_func::is_static(null));
self::assertFalse(nur_func::is_static(""));
self::assertFalse(nur_func::is_static("::"));
self::assertFalse(nur_func::is_static("xxx::"));
self::assertFalse(nur_func::is_static([]));
self::assertFalse(nur_func::is_static([""]));
self::assertFalse(nur_func::is_static([null, ""]));
self::assertFalse(nur_func::is_static(["xxx", ""]));
self::assertTrue(nur_func::is_static("::xxx"));
self::assertTrue(nur_func::is_static(["xxx"]));
self::assertTrue(nur_func::is_static([null, "yyy"]));
self::assertTrue(nur_func::is_static(["xxx", "yyy"]));
self::assertTrue(nur_func::is_static([null, "yyy", "aaa"]));
self::assertTrue(nur_func::is_static(["xxx", "yyy", "aaa"]));
}
function testFix_static() {
$class = "class";
$func = "::xxx";
nur_func::fix_static($func, $class);
self::assertSame("class::xxx", $func);
$func = ["xxx"];
nur_func::fix_static($func, $class);
self::assertSame(["class", "xxx"], $func);
$func = [null, "yyy"];
nur_func::fix_static($func, $class);
self::assertSame(["class", "yyy"], $func);
$func = ["xxx", "yyy"];
nur_func::fix_static($func, $class);
self::assertSame(["xxx", "yyy"], $func);
$func = [null, "yyy", "aaa"];
nur_func::fix_static($func, $class);
self::assertSame(["class", "yyy", "aaa"], $func);
$func = ["xxx", "yyy", "aaa"];
nur_func::fix_static($func, $class);
self::assertSame(["xxx", "yyy", "aaa"], $func);
}
function testIs_method() {
self::assertFalse(nur_func::is_method(null));
self::assertFalse(nur_func::is_method(""));
self::assertFalse(nur_func::is_method("->"));
self::assertFalse(nur_func::is_method([]));
self::assertFalse(nur_func::is_method([""]));
self::assertFalse(nur_func::is_method([null, "->"]));
self::assertFalse(nur_func::is_method(["xxx", "->"]));
self::assertTrue(nur_func::is_method("->xxx"));
self::assertTrue(nur_func::is_method(["->xxx"]));
self::assertTrue(nur_func::is_method([null, "->yyy"]));
self::assertTrue(nur_func::is_method(["xxx", "->yyy"]));
self::assertTrue(nur_func::is_method([null, "->yyy", "aaa"]));
self::assertTrue(nur_func::is_method(["xxx", "->yyy", "aaa"]));
}
function testFix_method() {
$object = new \stdClass();
$func= "->xxx";
nur_func::fix_method($func, $object);
self::assertSame([$object, "xxx"], $func);
$func= ["->xxx"];
nur_func::fix_method($func, $object);
self::assertSame([$object, "xxx"], $func);
$func= [null, "->yyy"];
nur_func::fix_method($func, $object);
self::assertSame([$object, "yyy"], $func);
$func= ["xxx", "->yyy"];
nur_func::fix_method($func, $object);
self::assertSame([$object, "yyy"], $func);
$func= [null, "->yyy", "aaa"];
nur_func::fix_method($func, $object);
self::assertSame([$object, "yyy", "aaa"], $func);
$func= ["xxx", "->yyy", "aaa"];
nur_func::fix_method($func, $object);
self::assertSame([$object, "yyy", "aaa"], $func);
}
function testCall() {
self::assertSame(36, nur_func::call("func36"));
self::assertSame(12, nur_func::call(TC::class."::method"));
self::assertSame(12, nur_func::call([TC::class, "method"]));
$closure = function() {
return 21;
};
self::assertSame(21, nur_func::call($closure));
}
function test_prepare_fill() {
# vérifier que les arguments sont bien remplis, en fonction du fait qu'ils
# soient obligatoires, facultatifs ou variadiques
# m1
self::assertSame([null], nur_func::call("func_m1"));
self::assertSame([null], nur_func::call("func_m1", null));
self::assertSame([null], nur_func::call("func_m1", null, null));
self::assertSame([null], nur_func::call("func_m1", null, null, null));
self::assertSame([null], nur_func::call("func_m1", null, null, null, null));
self::assertSame([1], nur_func::call("func_m1", 1));
self::assertSame([1], nur_func::call("func_m1", 1, 2));
self::assertSame([1], nur_func::call("func_m1", 1, 2, 3));
self::assertSame([1], nur_func::call("func_m1", 1, 2, 3, 4));
# o1
self::assertSame([9], nur_func::call("func_o1"));
self::assertSame([null], nur_func::call("func_o1", null));
self::assertSame([null], nur_func::call("func_o1", null, null));
self::assertSame([null], nur_func::call("func_o1", null, null, null));
self::assertSame([null], nur_func::call("func_o1", null, null, null, null));
self::assertSame([1], nur_func::call("func_o1", 1));
self::assertSame([1], nur_func::call("func_o1", 1, 2));
self::assertSame([1], nur_func::call("func_o1", 1, 2, 3));
self::assertSame([1], nur_func::call("func_o1", 1, 2, 3, 4));
# v
self::assertSame([], nur_func::call("func_v"));
self::assertSame([null], nur_func::call("func_v", null));
self::assertSame([null, null], nur_func::call("func_v", null, null));
self::assertSame([null, null, null], nur_func::call("func_v", null, null, null));
self::assertSame([null, null, null, null], nur_func::call("func_v", null, null, null, null));
self::assertSame([1], nur_func::call("func_v", 1));
self::assertSame([1, 2], nur_func::call("func_v", 1, 2));
self::assertSame([1, 2, 3], nur_func::call("func_v", 1, 2, 3));
self::assertSame([1, 2, 3, 4], nur_func::call("func_v", 1, 2, 3, 4));
# m1o1
self::assertSame([null, 9], nur_func::call("func_m1o1"));
self::assertSame([null, 9], nur_func::call("func_m1o1", null));
self::assertSame([null, null], nur_func::call("func_m1o1", null, null));
self::assertSame([null, null], nur_func::call("func_m1o1", null, null, null));
self::assertSame([null, null], nur_func::call("func_m1o1", null, null, null, null));
self::assertSame([1, 9], nur_func::call("func_m1o1", 1));
self::assertSame([1, 2], nur_func::call("func_m1o1", 1, 2));
self::assertSame([1, 2], nur_func::call("func_m1o1", 1, 2, 3));
self::assertSame([1, 2], nur_func::call("func_m1o1", 1, 2, 3, 4));
# m1v
self::assertSame([null], nur_func::call("func_m1v"));
self::assertSame([null], nur_func::call("func_m1v", null));
self::assertSame([null, null], nur_func::call("func_m1v", null, null));
self::assertSame([null, null, null], nur_func::call("func_m1v", null, null, null));
self::assertSame([null, null, null, null], nur_func::call("func_m1v", null, null, null, null));
self::assertSame([1], nur_func::call("func_m1v", 1));
self::assertSame([1, 2], nur_func::call("func_m1v", 1, 2));
self::assertSame([1, 2, 3], nur_func::call("func_m1v", 1, 2, 3));
self::assertSame([1, 2, 3, 4], nur_func::call("func_m1v", 1, 2, 3, 4));
# m1o1v
self::assertSame([null, 9], nur_func::call("func_m1o1v"));
self::assertSame([null, 9], nur_func::call("func_m1o1v", null));
self::assertSame([null, null], nur_func::call("func_m1o1v", null, null));
self::assertSame([null, null, null], nur_func::call("func_m1o1v", null, null, null));
self::assertSame([null, null, null, null], nur_func::call("func_m1o1v", null, null, null, null));
self::assertSame([1, 9], nur_func::call("func_m1o1v", 1));
self::assertSame([1, 2], nur_func::call("func_m1o1v", 1, 2));
self::assertSame([1, 2, 3], nur_func::call("func_m1o1v", 1, 2, 3));
self::assertSame([1, 2, 3, 4], nur_func::call("func_m1o1v", 1, 2, 3, 4));
# o1v
self::assertSame([9], nur_func::call("func_o1v"));
self::assertSame([null], nur_func::call("func_o1v", null));
self::assertSame([null, null], nur_func::call("func_o1v", null, null));
self::assertSame([null, null, null], nur_func::call("func_o1v", null, null, null));
self::assertSame([null, null, null, null], nur_func::call("func_o1v", null, null, null, null));
self::assertSame([1], nur_func::call("func_o1v", 1));
self::assertSame([1, 2], nur_func::call("func_o1v", 1, 2));
self::assertSame([1, 2, 3], nur_func::call("func_o1v", 1, 2, 3));
self::assertSame([1, 2, 3, 4], nur_func::call("func_o1v", 1, 2, 3, 4));
}
function testCall_all() {
$c1 = new C1();
$c2 = new C2();
$c3 = new C3();
self::assertSameValues([11, 12], nur_func::call_all(C1::class));
self::assertSameValues([11, 12, 21, 22], nur_func::call_all($c1));
self::assertSameValues([13, 11, 12], nur_func::call_all(C2::class));
self::assertSameValues([13, 23, 11, 12, 21, 22], nur_func::call_all($c2));
self::assertSameValues([111, 13, 12], nur_func::call_all(C3::class));
self::assertSameValues([111, 121, 13, 23, 12, 22], nur_func::call_all($c3));
$options = "conf";
self::assertSameValues([11], nur_func::call_all(C1::class, $options));
self::assertSameValues([11, 21], nur_func::call_all($c1, $options));
self::assertSameValues([11], nur_func::call_all(C2::class, $options));
self::assertSameValues([11, 21], nur_func::call_all($c2, $options));
self::assertSameValues([111], nur_func::call_all(C3::class, $options));
self::assertSameValues([111, 121], nur_func::call_all($c3, $options));
$options = ["prefix" => "conf"];
self::assertSameValues([11], nur_func::call_all(C1::class, $options));
self::assertSameValues([11, 21], nur_func::call_all($c1, $options));
self::assertSameValues([11], nur_func::call_all(C2::class, $options));
self::assertSameValues([11, 21], nur_func::call_all($c2, $options));
self::assertSameValues([111], nur_func::call_all(C3::class, $options));
self::assertSameValues([111, 121], nur_func::call_all($c3, $options));
self::assertSameValues([11, 12], nur_func::call_all($c1, ["include" => "x"]));
self::assertSameValues([11, 21], nur_func::call_all($c1, ["include" => "y"]));
self::assertSameValues([11, 12, 21], nur_func::call_all($c1, ["include" => ["x", "y"]]));
self::assertSameValues([21, 22], nur_func::call_all($c1, ["exclude" => "x"]));
self::assertSameValues([12, 22], nur_func::call_all($c1, ["exclude" => "y"]));
self::assertSameValues([22], nur_func::call_all($c1, ["exclude" => ["x", "y"]]));
self::assertSameValues([12], nur_func::call_all($c1, ["include" => "x", "exclude" => "y"]));
}
function testCons() {
$obj1 = nur_func::cons(WoCons::class, 1, 2, 3);
self::assertInstanceOf(WoCons::class, $obj1);
$obj2 = nur_func::cons(WithEmptyCons::class, 1, 2, 3);
self::assertInstanceOf(WithEmptyCons::class, $obj2);
$obj3 = nur_func::cons(WithCons::class, 1, 2, 3);
self::assertInstanceOf(WithCons::class, $obj3);
self::assertSame(1, $obj3->first);
}
}
class WoCons {
}
class WithEmptyCons {
function __construct() {
}
}
class WithCons {
public $first;
function __construct($first) {
$this->first = $first;
}
}
class TC {
static function method() {
return 12;
}
}
class C1 {
static function confps1_xy() {
return 11;
}
static function ps2_x() {
return 12;
}
function confp1_y() {
return 21;
}
function p2() {
return 22;
}
}
class C2 extends C1 {
static function ps3() {
return 13;
}
function p3() {
return 23;
}
}
class C3 extends C2 {
static function confps1_xy() {
return 111;
}
function confp1_y() {
return 121;
}
}
}