Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
b4e28ade02 | |||
d63a0cb704 | |||
91beea9c29 | |||
0e9be5f221 | |||
5e141b575e | |||
8ee13a85c0 | |||
2af417a193 | |||
d4cc8bfa42 | |||
ca129dfda4 | |||
2a50167241 | |||
146461a184 | |||
2a46c12e08 | |||
d241ce6561 | |||
86136e75a5 | |||
64c872cf3f | |||
ebbd9e06c0 | |||
5c6d55ed46 | |||
bab9ba81fe | |||
ecd01777c1 | |||
bd1f901b70 | |||
1536e091fb | |||
a8d55d329a | |||
60ab13ff84 | |||
f2614385fe |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
|||||||
|
/test_*
|
||||||
|
|
||||||
/.composer.lock.runphp
|
/.composer.lock.runphp
|
||||||
|
|
||||||
.~lock*#
|
.~lock*#
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
## Release 0.4.1p82 du 25/03/2025-08:47
|
||||||
|
|
||||||
## Release 0.4.1p74 du 25/03/2025-08:47
|
## Release 0.4.1p74 du 25/03/2025-08:47
|
||||||
|
|
||||||
* `5beb5e6` corriger la prise en compte du proxy
|
* `5beb5e6` corriger la prise en compte du proxy
|
||||||
|
59
bin/pman
59
bin/pman
@ -91,6 +91,48 @@ function init_config_action() {
|
|||||||
_push_branches
|
_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() {
|
function _ensure_main_branch() {
|
||||||
[ -n "$MAIN" ] || die "La branche MAIN n'a pas été définie"
|
[ -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é?)"
|
[ -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() {
|
function _ensure_develop_branch() {
|
||||||
[ -n "$DEVELOP" ] || die "La branche DEVELOP n'a pas été définie"
|
[ -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() {
|
function init_develop_action() {
|
||||||
@ -119,7 +161,7 @@ function init_develop_action() {
|
|||||||
$DEVELOP: une branche du même nom existe dans l'origine
|
$DEVELOP: une branche du même nom existe dans l'origine
|
||||||
git checkout $DEVELOP"
|
git checkout $DEVELOP"
|
||||||
_ensure_main_branch
|
_ensure_main_branch
|
||||||
_ensure_develop_branch
|
_ensure_develop_branch init
|
||||||
|
|
||||||
resolve_should_push
|
resolve_should_push
|
||||||
|
|
||||||
@ -137,7 +179,7 @@ $DEVELOP: une branche du même nom existe dans l'origine
|
|||||||
|
|
||||||
function _ensure_upstream_branch() {
|
function _ensure_upstream_branch() {
|
||||||
[ -n "$UPSTREAM" ] || die "La branche UPSTREAM n'a pas été définie"
|
[ -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() {
|
function init_upstream_action() {
|
||||||
@ -148,7 +190,7 @@ function init_upstream_action() {
|
|||||||
$UPSTREAM: une branche du même nom existe dans l'origine
|
$UPSTREAM: une branche du même nom existe dans l'origine
|
||||||
git checkout $UPSTREAM"
|
git checkout $UPSTREAM"
|
||||||
_ensure_develop_branch
|
_ensure_develop_branch
|
||||||
_ensure_upstream_branch
|
_ensure_upstream_branch init
|
||||||
|
|
||||||
resolve_should_push
|
resolve_should_push
|
||||||
|
|
||||||
@ -182,7 +224,7 @@ $UPSTREAM: une branche du même nom existe dans l'origine
|
|||||||
|
|
||||||
function _ensure_dist_branch() {
|
function _ensure_dist_branch() {
|
||||||
[ -n "$DIST" ] || die "La branche DIST n'a pas été définie"
|
[ -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() {
|
function init_dist_action() {
|
||||||
@ -193,7 +235,7 @@ function init_dist_action() {
|
|||||||
$DIST: une branche du même nom existe dans l'origine
|
$DIST: une branche du même nom existe dans l'origine
|
||||||
git checkout $DIST"
|
git checkout $DIST"
|
||||||
_ensure_main_branch
|
_ensure_main_branch
|
||||||
_ensure_dist_branch
|
_ensure_dist_branch init
|
||||||
|
|
||||||
resolve_should_push
|
resolve_should_push
|
||||||
|
|
||||||
@ -242,6 +284,7 @@ function init_action() {
|
|||||||
case "$what" in
|
case "$what" in
|
||||||
init|repo|r) init_repo_action "$@";;
|
init|repo|r) init_repo_action "$@";;
|
||||||
config) init_config_action "$@";;
|
config) init_config_action "$@";;
|
||||||
|
composer) init_composer_action "$@";;
|
||||||
main|m) checkout_main_action;;
|
main|m) checkout_main_action;;
|
||||||
develop|dev|d) init_develop_action "$@";;
|
develop|dev|d) init_develop_action "$@";;
|
||||||
upstream|up|u) init_upstream_action "$@";;
|
upstream|up|u) init_upstream_action "$@";;
|
||||||
@ -263,7 +306,8 @@ Origin=
|
|||||||
ForceCreate=
|
ForceCreate=
|
||||||
args=(
|
args=(
|
||||||
"gérer un projet git"
|
"gérer un projet git"
|
||||||
"repo|config|develop|upstream|dist
|
"repo|config|composer
|
||||||
|
develop|upstream|dist
|
||||||
|
|
||||||
INITIALISATION
|
INITIALISATION
|
||||||
|
|
||||||
@ -272,6 +316,7 @@ configurer certaines branches du dépôt si elles n'existent pas déjà
|
|||||||
|
|
||||||
repo
|
repo
|
||||||
initialiser un dépôt vide et créer les branches $MAIN et $DEVELOP
|
initialiser un dépôt vide et créer les branches $MAIN et $DEVELOP
|
||||||
|
|
||||||
develop
|
develop
|
||||||
créer la branche $DEVELOP
|
créer la branche $DEVELOP
|
||||||
upstream
|
upstream
|
||||||
|
@ -24,6 +24,8 @@
|
|||||||
"ext-posix": "*",
|
"ext-posix": "*",
|
||||||
"ext-pcntl": "*",
|
"ext-pcntl": "*",
|
||||||
"ext-curl": "*",
|
"ext-curl": "*",
|
||||||
|
"ext-pdo": "*",
|
||||||
|
"ext-pgsql": "*",
|
||||||
"ext-sqlite3": "*"
|
"ext-sqlite3": "*"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
4
composer.lock
generated
4
composer.lock
generated
@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "266a079e97f3ceecc2cc0a84d6b9743b",
|
"content-hash": "a8b9dc80255663640bda855729ef2d47",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "symfony/deprecation-contracts",
|
"name": "symfony/deprecation-contracts",
|
||||||
@ -2022,6 +2022,8 @@
|
|||||||
"ext-posix": "*",
|
"ext-posix": "*",
|
||||||
"ext-pcntl": "*",
|
"ext-pcntl": "*",
|
||||||
"ext-curl": "*",
|
"ext-curl": "*",
|
||||||
|
"ext-pdo": "*",
|
||||||
|
"ext-pgsql": "*",
|
||||||
"ext-sqlite3": "*"
|
"ext-sqlite3": "*"
|
||||||
},
|
},
|
||||||
"plugin-api-version": "2.2.0"
|
"plugin-api-version": "2.2.0"
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
namespace nulib;
|
namespace nulib;
|
||||||
|
|
||||||
use ArrayAccess;
|
use ArrayAccess;
|
||||||
use nulib\php\nur_func;
|
use nulib\php\func;
|
||||||
use Traversable;
|
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 = [];
|
$result = [];
|
||||||
if ($array !== null) {
|
if ($array !== null) {
|
||||||
$ctx = nur_func::_prepare($callback);
|
$func = func::with($func);
|
||||||
foreach ($array as $key => $value) {
|
foreach ($array as $key => $value) {
|
||||||
$result[$key] = nur_func::_call($ctx, [$value, $key]);
|
$result[$key] = $func->invoke([$value, $key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $result;
|
return $result;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db;
|
namespace nulib\db;
|
||||||
|
|
||||||
use nulib\php\nur_func;
|
use nulib\php\func;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
use Traversable;
|
use Traversable;
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ class Capacitor implements ITransactor {
|
|||||||
if ($func !== null) {
|
if ($func !== null) {
|
||||||
$commited = false;
|
$commited = false;
|
||||||
try {
|
try {
|
||||||
nur_func::call($func, $this);
|
func::call($func, $this);
|
||||||
if ($commit) {
|
if ($commit) {
|
||||||
$this->commit();
|
$this->commit();
|
||||||
$commited = true;
|
$commited = true;
|
||||||
@ -120,10 +120,6 @@ class Capacitor implements ITransactor {
|
|||||||
if ($db->inTransaction()) $db->rollback();
|
if ($db->inTransaction()) $db->rollback();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCreateSql(): string {
|
|
||||||
return $this->storage->_getCreateSql($this->channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
function exists(): bool {
|
function exists(): bool {
|
||||||
return $this->storage->_exists($this->channel);
|
return $this->storage->_exists($this->channel);
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@ class CapacitorChannel {
|
|||||||
|
|
||||||
const PRIMARY_KEYS = null;
|
const PRIMARY_KEYS = null;
|
||||||
|
|
||||||
|
const MIGRATION = null;
|
||||||
|
|
||||||
const MANAGE_TRANSACTIONS = true;
|
const MANAGE_TRANSACTIONS = true;
|
||||||
|
|
||||||
const EACH_COMMIT_THRESHOLD = 100;
|
const EACH_COMMIT_THRESHOLD = 100;
|
||||||
@ -63,15 +65,29 @@ class CapacitorChannel {
|
|||||||
$this->created = false;
|
$this->created = false;
|
||||||
$columnDefinitions = cl::withn(static::COLUMN_DEFINITIONS);
|
$columnDefinitions = cl::withn(static::COLUMN_DEFINITIONS);
|
||||||
$primaryKeys = cl::withn(static::PRIMARY_KEYS);
|
$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;
|
$index = 0;
|
||||||
foreach ($columnDefinitions as $col => $def) {
|
foreach ($columnDefinitions as $col => $def) {
|
||||||
if ($col === $index) {
|
if ($col === $index) {
|
||||||
|
# si définition séquentielle, seules les définitions de clé
|
||||||
|
# primaires sont supportées
|
||||||
$index++;
|
$index++;
|
||||||
if (preg_match('/\bprimary\s+key\s+\((.+)\)/i', $def, $ms)) {
|
if (preg_match('/\bprimary\s+key\s+\((.+)\)/i', $def, $ms)) {
|
||||||
$primaryKeys = preg_split('/\s*,\s*/', trim($ms[1]));
|
$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 {
|
} 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)) {
|
if (preg_match('/\bprimary\s+key\b/i', $def)) {
|
||||||
$primaryKeys[] = $col;
|
$primaryKeys[] = $col;
|
||||||
}
|
}
|
||||||
@ -80,6 +96,7 @@ class CapacitorChannel {
|
|||||||
}
|
}
|
||||||
$this->columnDefinitions = $columnDefinitions;
|
$this->columnDefinitions = $columnDefinitions;
|
||||||
$this->primaryKeys = $primaryKeys;
|
$this->primaryKeys = $primaryKeys;
|
||||||
|
$this->migration = $migration;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected string $name;
|
protected string $name;
|
||||||
@ -192,6 +209,12 @@ class CapacitorChannel {
|
|||||||
return $this->columnDefinitions;
|
return $this->columnDefinitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected ?array $migration;
|
||||||
|
|
||||||
|
function getMigration(): ?array {
|
||||||
|
return $this->migration;
|
||||||
|
}
|
||||||
|
|
||||||
protected ?array $primaryKeys;
|
protected ?array $primaryKeys;
|
||||||
|
|
||||||
function getPrimaryKeys(): ?array {
|
function getPrimaryKeys(): ?array {
|
||||||
@ -245,9 +268,6 @@ class CapacitorChannel {
|
|||||||
return $serial !== null? unserialize($serial): null;
|
return $serial !== null? unserialize($serial): null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SERIAL_DEFINITION = "mediumtext";
|
|
||||||
const SUM_DEFINITION = "varchar(40)";
|
|
||||||
|
|
||||||
final function sum(?string $serial, $value=null): ?string {
|
final function sum(?string $serial, $value=null): ?string {
|
||||||
if ($serial === null) $serial = $this->serialize($value);
|
if ($serial === null) $serial = $this->serialize($value);
|
||||||
return $serial !== null? sha1($serial): null;
|
return $serial !== null? sha1($serial): null;
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
namespace nulib\db;
|
namespace nulib\db;
|
||||||
|
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
|
use nulib\db\_private\_migration;
|
||||||
use nulib\db\cache\cache;
|
use nulib\db\cache\cache;
|
||||||
use nulib\php\nur_func;
|
use nulib\php\func;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
use Traversable;
|
use Traversable;
|
||||||
|
|
||||||
@ -35,14 +36,18 @@ abstract class CapacitorStorage {
|
|||||||
/** DOIT être défini dans les classes dérivées */
|
/** DOIT être défini dans les classes dérivées */
|
||||||
const PRIMARY_KEY_DEFINITION = null;
|
const PRIMARY_KEY_DEFINITION = null;
|
||||||
|
|
||||||
|
const SERDATA_DEFINITION = "mediumtext";
|
||||||
|
const SERSUM_DEFINITION = "varchar(40)";
|
||||||
|
const SERTS_DEFINITION = "datetime";
|
||||||
|
|
||||||
const COLUMN_DEFINITIONS = [
|
const COLUMN_DEFINITIONS = [
|
||||||
"item__" => CapacitorChannel::SERIAL_DEFINITION,
|
"item__" => "serdata",
|
||||||
"item__sum_" => CapacitorChannel::SUM_DEFINITION,
|
"item__sum_" => "sersum",
|
||||||
"created_" => "datetime",
|
"created_" => "serts",
|
||||||
"modified_" => "datetime",
|
"modified_" => "serts",
|
||||||
];
|
];
|
||||||
|
|
||||||
protected function ColumnDefinitions(CapacitorChannel $channel): array {
|
protected function ColumnDefinitions(CapacitorChannel $channel, bool $ignoreMigrations=false): array {
|
||||||
$definitions = [];
|
$definitions = [];
|
||||||
if ($channel->getPrimaryKeys() === null) {
|
if ($channel->getPrimaryKeys() === null) {
|
||||||
$definitions[] = static::PRIMARY_KEY_DEFINITION;
|
$definitions[] = static::PRIMARY_KEY_DEFINITION;
|
||||||
@ -56,16 +61,31 @@ abstract class CapacitorStorage {
|
|||||||
$constraints = [];
|
$constraints = [];
|
||||||
$index = 0;
|
$index = 0;
|
||||||
foreach ($tmp as $col => $def) {
|
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) {
|
if ($col === $index) {
|
||||||
$index++;
|
$index++;
|
||||||
$constraints[] = $def;
|
$constraints[] = $def;
|
||||||
} else {
|
} elseif (is_array($def)) {
|
||||||
|
# éventuellement, ignorer les migrations
|
||||||
|
$def = implode(" ", $def);
|
||||||
|
if ($def && !$ignoreMigrations) {
|
||||||
$definitions[$col] = $def;
|
$definitions[$col] = $def;
|
||||||
}
|
}
|
||||||
|
} elseif (is_scalar($def)) {
|
||||||
|
$definitions[$col] = strval($def);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return cl::merge($definitions, $constraints);
|
return cl::merge($definitions, $constraints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getMigration(CapacitorChannel $channel): ?array {
|
||||||
|
return $channel->getMigration();
|
||||||
|
}
|
||||||
|
|
||||||
/** sérialiser les valeurs qui doivent l'être dans $values */
|
/** sérialiser les valeurs qui doivent l'être dans $values */
|
||||||
protected function serialize(CapacitorChannel $channel, ?array $values): ?array {
|
protected function serialize(CapacitorChannel $channel, ?array $values): ?array {
|
||||||
if ($values === null) return null;
|
if ($values === null) return null;
|
||||||
@ -128,11 +148,10 @@ abstract class CapacitorStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function _createSql(CapacitorChannel $channel): array {
|
protected function _createSql(CapacitorChannel $channel): array {
|
||||||
$cols = $this->ColumnDefinitions($channel);
|
|
||||||
return [
|
return [
|
||||||
"create table if not exists",
|
"create table if not exists",
|
||||||
"table" => $channel->getTableName(),
|
"table" => $channel->getTableName(),
|
||||||
"cols" => $cols,
|
"cols" => $this->ColumnDefinitions($channel, true),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,20 +166,45 @@ $sql;
|
|||||||
EOT;
|
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 */
|
const CHANNELS_TABLE = "_channels";
|
||||||
function getCreateSql(?string $channel): string {
|
const CHANNELS_COLS = [
|
||||||
return $this->_getCreateSql($this->getChannel($channel));
|
"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 {
|
protected function _afterCreate(CapacitorChannel $channel): void {
|
||||||
|
$db = $this->db();
|
||||||
|
$db->exec($this->_createChannelsSql());
|
||||||
|
$db->exec($this->_addToChannelsSql($channel));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function _create(CapacitorChannel $channel): void {
|
protected function _create(CapacitorChannel $channel): void {
|
||||||
$channel->ensureSetup();
|
$channel->ensureSetup();
|
||||||
if (!$channel->isCreated()) {
|
if (!$channel->isCreated()) {
|
||||||
$this->db->exec($this->_createSql($channel));
|
$this->_getMigration($channel)->migrate($this->db());
|
||||||
$this->_afterCreate($channel);
|
$this->_afterCreate($channel);
|
||||||
$channel->setCreated();
|
$channel->setCreated();
|
||||||
}
|
}
|
||||||
@ -183,12 +227,28 @@ EOT;
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function _beforeReset(CapacitorChannel $channel): void {
|
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é */
|
/** supprimer le canal spécifié */
|
||||||
function _reset(CapacitorChannel $channel, bool $recreate=false): void {
|
function _reset(CapacitorChannel $channel, bool $recreate=false): void {
|
||||||
$this->_beforeReset($channel);
|
$this->_beforeReset($channel);
|
||||||
$this->db->exec([
|
$this->db()->exec([
|
||||||
"drop table if exists",
|
"drop table if exists",
|
||||||
$channel->getTableName(),
|
$channel->getTableName(),
|
||||||
]);
|
]);
|
||||||
@ -230,10 +290,7 @@ EOT;
|
|||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$args ??= [];
|
$args ??= [];
|
||||||
|
|
||||||
$initFunc = [$channel, "getItemValues"];
|
$values = func::call([$channel, "getItemValues"], $item, ...$args);
|
||||||
$initArgs = $args;
|
|
||||||
nur_func::ensure_func($initFunc, null, $initArgs);
|
|
||||||
$values = nur_func::call($initFunc, $item, ...$initArgs);
|
|
||||||
if ($values === [false]) return 0;
|
if ($values === [false]) return 0;
|
||||||
|
|
||||||
$row = cl::merge(
|
$row = cl::merge(
|
||||||
@ -259,9 +316,7 @@ EOT;
|
|||||||
"modified_" => $now,
|
"modified_" => $now,
|
||||||
]);
|
]);
|
||||||
$insert = true;
|
$insert = true;
|
||||||
$initFunc = [$channel, "onCreate"];
|
$initFunc = func::with([$channel, "onCreate"], $args);
|
||||||
$initArgs = $args;
|
|
||||||
nur_func::ensure_func($initFunc, null, $initArgs);
|
|
||||||
$values = $this->unserialize($channel, $row);
|
$values = $this->unserialize($channel, $row);
|
||||||
$pvalues = null;
|
$pvalues = null;
|
||||||
} else {
|
} else {
|
||||||
@ -276,14 +331,12 @@ EOT;
|
|||||||
} else {
|
} else {
|
||||||
$row = cl::merge($prow, $row);
|
$row = cl::merge($prow, $row);
|
||||||
}
|
}
|
||||||
$initFunc = [$channel, "onUpdate"];
|
$initFunc = func::with([$channel, "onUpdate"], $args);
|
||||||
$initArgs = $args;
|
|
||||||
nur_func::ensure_func($initFunc, null, $initArgs);
|
|
||||||
$values = $this->unserialize($channel, $row);
|
$values = $this->unserialize($channel, $row);
|
||||||
$pvalues = $this->unserialize($channel, $prow);
|
$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 ($updates === [false]) return 0;
|
||||||
if (is_array($updates) && $updates) {
|
if (is_array($updates) && $updates) {
|
||||||
if ($insert === null) $insert = false;
|
if ($insert === null) $insert = false;
|
||||||
@ -295,8 +348,10 @@ EOT;
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($func !== null) {
|
if ($func !== null) {
|
||||||
nur_func::ensure_func($func, $channel, $args);
|
$updates = func::with($func)
|
||||||
$updates = nur_func::call($func, $item, $values, $pvalues, ...$args);
|
->prependArgs([$item, $values, $pvalues])
|
||||||
|
->bind($channel, true)
|
||||||
|
->invoke();
|
||||||
if ($updates === [false]) return 0;
|
if ($updates === [false]) return 0;
|
||||||
if (is_array($updates) && $updates) {
|
if (is_array($updates) && $updates) {
|
||||||
if ($insert === null) $insert = false;
|
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 {
|
function _each(CapacitorChannel $channel, $filter, $func, ?array $args, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
|
||||||
$this->_create($channel);
|
$this->_create($channel);
|
||||||
if ($func === null) $func = CapacitorChannel::onEach;
|
if ($func === null) $func = CapacitorChannel::onEach;
|
||||||
nur_func::ensure_func($func, $channel, $args);
|
$onEach = func::with($func)->bind($channel, true);
|
||||||
$onEach = nur_func::_prepare($func);
|
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
||||||
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
||||||
@ -528,7 +582,7 @@ EOT;
|
|||||||
$all = $this->_allCached("each", $channel, $filter, $mergeQuery);
|
$all = $this->_allCached("each", $channel, $filter, $mergeQuery);
|
||||||
foreach ($all as $values) {
|
foreach ($all as $values) {
|
||||||
$rowIds = $this->getRowIds($channel, $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 (is_array($updates) && $updates) {
|
||||||
if (!array_key_exists("modified_", $updates)) {
|
if (!array_key_exists("modified_", $updates)) {
|
||||||
$updates["modified_"] = date("Y-m-d H:i:s");
|
$updates["modified_"] = date("Y-m-d H:i:s");
|
||||||
@ -579,8 +633,7 @@ EOT;
|
|||||||
function _delete(CapacitorChannel $channel, $filter, $func, ?array $args): int {
|
function _delete(CapacitorChannel $channel, $filter, $func, ?array $args): int {
|
||||||
$this->_create($channel);
|
$this->_create($channel);
|
||||||
if ($func === null) $func = CapacitorChannel::onDelete;
|
if ($func === null) $func = CapacitorChannel::onDelete;
|
||||||
nur_func::ensure_func($func, $channel, $args);
|
$onEach = func::with($func)->bind($channel, true);
|
||||||
$onEach = nur_func::_prepare($func);
|
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
||||||
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
||||||
@ -596,7 +649,7 @@ EOT;
|
|||||||
$all = $this->_allCached("delete", $channel, $filter);
|
$all = $this->_allCached("delete", $channel, $filter);
|
||||||
foreach ($all as $values) {
|
foreach ($all as $values) {
|
||||||
$rowIds = $this->getRowIds($channel, $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) {
|
if ($delete) {
|
||||||
$db->exec([
|
$db->exec([
|
||||||
"delete",
|
"delete",
|
||||||
|
@ -15,5 +15,9 @@ interface IDatabase extends ITransactor {
|
|||||||
|
|
||||||
function one($query, ?array $params=null): ?array;
|
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;
|
function all($query, ?array $params=null, $primaryKeys=null): iterable;
|
||||||
}
|
}
|
||||||
|
@ -11,12 +11,13 @@ interface ITransactor {
|
|||||||
*/
|
*/
|
||||||
function willUpdate(...$transactors): self;
|
function willUpdate(...$transactors): self;
|
||||||
|
|
||||||
|
/** Indiquer si une transaction est en cours */
|
||||||
function inTransaction(): bool;
|
function inTransaction(): bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* démarrer une transaction
|
* 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,
|
* transaction. si une erreur se produit lors de l'appel de la fonction,
|
||||||
* annuler la transaction
|
* annuler la transaction
|
||||||
*
|
*
|
||||||
@ -24,7 +25,9 @@ interface ITransactor {
|
|||||||
*/
|
*/
|
||||||
function beginTransaction(?callable $func=null, bool $commit=true): void;
|
function beginTransaction(?callable $func=null, bool $commit=true): void;
|
||||||
|
|
||||||
|
/** valider la transaction */
|
||||||
function commit(): void;
|
function commit(): void;
|
||||||
|
|
||||||
|
/** annuler la transaction */
|
||||||
function rollback(): void;
|
function rollback(): void;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,255 +5,57 @@ use nulib\cl;
|
|||||||
use nulib\str;
|
use nulib\str;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
|
|
||||||
abstract class _base {
|
abstract class _base extends _common {
|
||||||
protected static function consume(string $pattern, string &$string, ?array &$ms=null): bool {
|
protected static function verifix(&$sql, ?array &$bindings=null, ?array &$meta=null): void {
|
||||||
if (!preg_match("/^$pattern/i", $string, $ms)) return false;
|
if (is_array($sql)) {
|
||||||
$string = substr($string, strlen($ms[0]));
|
$prefix = $sql[0] ?? null;
|
||||||
return true;
|
if ($prefix === null) {
|
||||||
}
|
throw new ValueException("requête invalide");
|
||||||
|
} elseif (_create::isa($prefix)) {
|
||||||
/** fusionner toutes les parties séquentielles d'une requête */
|
$sql = _create::parse($sql, $bindings);
|
||||||
protected static function merge_seq(array $query): string {
|
$meta = ["isa" => "create", "type" => "ddl"];
|
||||||
$index = 0;
|
} elseif (_select::isa($prefix)) {
|
||||||
$sql = "";
|
$sql = _select::parse($sql, $bindings);
|
||||||
foreach ($query as $key => $value) {
|
$meta = ["isa" => "select", "type" => "dql"];
|
||||||
if ($key === $index) {
|
} elseif (_insert::isa($prefix)) {
|
||||||
$index++;
|
$sql = _insert::parse($sql, $bindings);
|
||||||
if ($sql && !str::ends_with(" ", $sql) && !str::starts_with(" ", $value)) {
|
$meta = ["isa" => "insert", "type" => "dml"];
|
||||||
$sql .= " ";
|
} elseif (_update::isa($prefix)) {
|
||||||
}
|
$sql = _update::parse($sql, $bindings);
|
||||||
$sql .= $value;
|
$meta = ["isa" => "update", "type" => "dml"];
|
||||||
}
|
} elseif (_delete::isa($prefix)) {
|
||||||
}
|
$sql = _delete::parse($sql, $bindings);
|
||||||
return $sql;
|
$meta = ["isa" => "delete", "type" => "dml"];
|
||||||
}
|
} elseif (_generic::isa($prefix)) {
|
||||||
|
$sql = _generic::parse($sql, $bindings);
|
||||||
protected static function is_sep(&$cond): bool {
|
$meta = ["isa" => "generic", "type" => null];
|
||||||
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 {
|
} else {
|
||||||
# condition litérale
|
throw ValueException::invalid_kind($sql, "query");
|
||||||
$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 {
|
} else {
|
||||||
$param = "$param0$i";
|
if (!is_string($sql)) $sql = strval($sql);
|
||||||
$parts[] = ":$param";
|
if (_create::isa($sql)) {
|
||||||
$bindings[$param] = $condvalue;
|
$meta = ["isa" => "create", "type" => "ddl"];
|
||||||
if ($i === false) $i = 2;
|
} elseif (_select::isa($sql)) {
|
||||||
else $i++;
|
$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 {
|
static function with($sql, ?array $params=null): array {
|
||||||
if (!$values) return;
|
static::verifix($sql, $params);
|
||||||
$index = 0;
|
return [$sql, $params];
|
||||||
$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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract protected static function verifix(&$sql, ?array &$bindinds=null, ?array &$meta=null): void;
|
|
||||||
|
|
||||||
function __construct($sql, ?array $bindings=null) {
|
function __construct($sql, ?array $bindings=null) {
|
||||||
static::verifix($sql, $bindings, $meta);
|
static::verifix($sql, $bindings, $meta);
|
||||||
|
255
php/src/db/_private/_common.php
Normal file
255
php/src/db/_private/_common.php
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\pdo;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
use nulib\php\nur_func;
|
use nulib\db\IDatabase;
|
||||||
|
use nulib\php\func;
|
||||||
|
|
||||||
class _config {
|
class _config {
|
||||||
static function with($configs): self {
|
static function with($configs): self {
|
||||||
@ -23,13 +24,12 @@ class _config {
|
|||||||
/** @var array */
|
/** @var array */
|
||||||
protected $configs;
|
protected $configs;
|
||||||
|
|
||||||
function configure(Pdo $pdo): void {
|
function configure(IDatabase $db): void {
|
||||||
foreach ($this->configs as $key => $config) {
|
foreach ($this->configs as $key => $config) {
|
||||||
if (is_string($config) && !nur_func::is_method($config)) {
|
if (is_string($config) && !func::is_method($config)) {
|
||||||
$pdo->exec($config);
|
$db->exec($config);
|
||||||
} else {
|
} else {
|
||||||
nur_func::ensure_func($config, $this, $args);
|
func::with($config)->bind($this, true)->invoke([$db, $key]);
|
||||||
nur_func::call($config, $pdo, $key, ...$args);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\_private;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
class _create {
|
class _create extends _common {
|
||||||
const SCHEMA = [
|
const SCHEMA = [
|
||||||
"prefix" => "?string",
|
"prefix" => "?string",
|
||||||
"table" => "string",
|
"table" => "string",
|
||||||
@ -9,4 +9,46 @@ class _create {
|
|||||||
"cols" => "?array",
|
"cols" => "?array",
|
||||||
"suffix" => "?string",
|
"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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,48 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\_private;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
class _delete {
|
class _delete extends _common {
|
||||||
const SCHEMA = [
|
const SCHEMA = [
|
||||||
"prefix" => "?string",
|
"prefix" => "?string",
|
||||||
"from" => "?string",
|
"from" => "?string",
|
||||||
"where" => "?array",
|
"where" => "?array",
|
||||||
"suffix" => "?string",
|
"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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,21 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\_private;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
class _generic {
|
use nulib\cl;
|
||||||
|
use nulib\ValueException;
|
||||||
|
|
||||||
|
class _generic extends _common {
|
||||||
const SCHEMA = [
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\_private;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
class _insert {
|
use nulib\cl;
|
||||||
|
use nulib\ValueException;
|
||||||
|
|
||||||
|
class _insert extends _common {
|
||||||
const SCHEMA = [
|
const SCHEMA = [
|
||||||
"prefix" => "?string",
|
"prefix" => "?string",
|
||||||
"into" => "?string",
|
"into" => "?string",
|
||||||
@ -10,4 +13,79 @@ class _insert {
|
|||||||
"values" => "?array",
|
"values" => "?array",
|
||||||
"suffix" => "?string",
|
"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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
72
php/src/db/_private/_migration.php
Normal file
72
php/src/db/_private/_migration.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\_private;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
class _select {
|
use nulib\cl;
|
||||||
|
use nulib\str;
|
||||||
|
use nulib\ValueException;
|
||||||
|
|
||||||
|
class _select extends _common {
|
||||||
const SCHEMA = [
|
const SCHEMA = [
|
||||||
"prefix" => "?string",
|
"prefix" => "?string",
|
||||||
"schema" => "?array",
|
"schema" => "?array",
|
||||||
@ -14,4 +18,164 @@ class _select {
|
|||||||
"having" => "?array",
|
"having" => "?array",
|
||||||
"suffix" => "?string",
|
"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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\_private;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
class _update {
|
class _update extends _common {
|
||||||
const SCHEMA = [
|
const SCHEMA = [
|
||||||
"prefix" => "?string",
|
"prefix" => "?string",
|
||||||
"table" => "?string",
|
"table" => "?string",
|
||||||
@ -11,4 +11,43 @@ class _update {
|
|||||||
"where" => "?array",
|
"where" => "?array",
|
||||||
"suffix" => "?string",
|
"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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\mysql;
|
namespace nulib\db\mysql;
|
||||||
|
|
||||||
|
use nulib\cl;
|
||||||
use nulib\db\CapacitorChannel;
|
use nulib\db\CapacitorChannel;
|
||||||
use nulib\db\CapacitorStorage;
|
use nulib\db\CapacitorStorage;
|
||||||
|
|
||||||
@ -12,8 +13,7 @@ class MysqlStorage extends CapacitorStorage {
|
|||||||
$this->db = Mysql::with($mysql);
|
$this->db = Mysql::with($mysql);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var Mysql */
|
protected Mysql $db;
|
||||||
protected $db;
|
|
||||||
|
|
||||||
function db(): Mysql {
|
function db(): Mysql {
|
||||||
return $this->db;
|
return $this->db;
|
||||||
@ -23,17 +23,35 @@ class MysqlStorage extends CapacitorStorage {
|
|||||||
"id_" => "integer primary key auto_increment",
|
"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 {
|
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());
|
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 {
|
function _exists(CapacitorChannel $channel): bool {
|
||||||
$db = $this->db;
|
$mysql = $this->db;
|
||||||
$tableName = $db->get([
|
$tableName = $mysql->get([
|
||||||
"select table_name from information_schema.tables",
|
"select table_name from information_schema.tables",
|
||||||
"where" => [
|
"where" => [
|
||||||
"table_schema" => $db->getDbname(),
|
"table_schema" => $mysql->getDbname(),
|
||||||
"table_name" => $channel->getTableName(),
|
"table_name" => $channel->getTableName(),
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
31
php/src/db/mysql/_mysqlMigration.php
Normal file
31
php/src/db/mysql/_mysqlMigration.php
Normal 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",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
8
php/src/db/mysql/_mysqlQuery.php
Normal file
8
php/src/db/mysql/_mysqlQuery.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\db\mysql;
|
||||||
|
|
||||||
|
use nulib\db\pdo\_pdoQuery;
|
||||||
|
|
||||||
|
class _mysqlQuery extends _pdoQuery {
|
||||||
|
const DEBUG_QUERIES = false;
|
||||||
|
}
|
@ -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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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];
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,10 +3,11 @@ namespace nulib\db\pdo;
|
|||||||
|
|
||||||
use Generator;
|
use Generator;
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
|
use nulib\db\_private\_config;
|
||||||
use nulib\db\_private\Tvalues;
|
use nulib\db\_private\Tvalues;
|
||||||
use nulib\db\IDatabase;
|
use nulib\db\IDatabase;
|
||||||
use nulib\db\ITransactor;
|
use nulib\db\ITransactor;
|
||||||
use nulib\php\nur_func;
|
use nulib\php\func;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
|
|
||||||
class Pdo implements IDatabase {
|
class Pdo implements IDatabase {
|
||||||
@ -21,7 +22,7 @@ class Pdo implements IDatabase {
|
|||||||
"dbconn" => $pdo->dbconn,
|
"dbconn" => $pdo->dbconn,
|
||||||
"options" => $pdo->options,
|
"options" => $pdo->options,
|
||||||
"config" => $pdo->config,
|
"config" => $pdo->config,
|
||||||
"migrate" => $pdo->migration,
|
"migration" => $pdo->migration,
|
||||||
], $params));
|
], $params));
|
||||||
} else {
|
} else {
|
||||||
return new static($pdo, $params);
|
return new static($pdo, $params);
|
||||||
@ -49,7 +50,7 @@ class Pdo implements IDatabase {
|
|||||||
|
|
||||||
protected const CONFIG = null;
|
protected const CONFIG = null;
|
||||||
|
|
||||||
protected const MIGRATE = null;
|
protected const MIGRATION = null;
|
||||||
|
|
||||||
const dbconn_SCHEMA = [
|
const dbconn_SCHEMA = [
|
||||||
"name" => "string",
|
"name" => "string",
|
||||||
@ -62,7 +63,7 @@ class Pdo implements IDatabase {
|
|||||||
"options" => ["?array|callable"],
|
"options" => ["?array|callable"],
|
||||||
"replace_config" => ["?array|callable"],
|
"replace_config" => ["?array|callable"],
|
||||||
"config" => ["?array|callable"],
|
"config" => ["?array|callable"],
|
||||||
"migrate" => ["?array|string|callable"],
|
"migration" => ["?array|string|callable"],
|
||||||
"auto_open" => ["bool", true],
|
"auto_open" => ["bool", true],
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -93,7 +94,7 @@ class Pdo implements IDatabase {
|
|||||||
}
|
}
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
# migrations
|
# migrations
|
||||||
$this->migration = $params["migrate"] ?? static::MIGRATE;
|
$this->migration = $params["migration"] ?? static::MIGRATION;
|
||||||
#
|
#
|
||||||
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
|
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
|
||||||
if ($params["auto_open"] ?? $defaultAutoOpen) {
|
if ($params["auto_open"] ?? $defaultAutoOpen) {
|
||||||
@ -104,7 +105,7 @@ class Pdo implements IDatabase {
|
|||||||
protected ?array $dbconn;
|
protected ?array $dbconn;
|
||||||
|
|
||||||
/** @var array|callable */
|
/** @var array|callable */
|
||||||
protected array $options;
|
protected $options;
|
||||||
|
|
||||||
/** @var array|string|callable */
|
/** @var array|string|callable */
|
||||||
protected $config;
|
protected $config;
|
||||||
@ -119,8 +120,7 @@ class Pdo implements IDatabase {
|
|||||||
$dbconn = $this->dbconn;
|
$dbconn = $this->dbconn;
|
||||||
$options = $this->options;
|
$options = $this->options;
|
||||||
if (is_callable($options)) {
|
if (is_callable($options)) {
|
||||||
nur_func::ensure_func($options, $this, $args);
|
$options = func::with($options)->bind($this, true)->invoke();
|
||||||
$options = nur_func::call($options, ...$args);
|
|
||||||
}
|
}
|
||||||
$this->db = new \PDO($dbconn["name"], $dbconn["user"], $dbconn["pass"], $options);
|
$this->db = new \PDO($dbconn["name"], $dbconn["user"], $dbconn["pass"], $options);
|
||||||
_config::with($this->config)->configure($this);
|
_config::with($this->config)->configure($this);
|
||||||
@ -143,21 +143,16 @@ class Pdo implements IDatabase {
|
|||||||
return $this->db()->exec($query);
|
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) {
|
function exec($query, ?array $params=null) {
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$query = new _query_base($query, $params);
|
$query = new _pdoQuery($query, $params);
|
||||||
if ($query->useStmt($db, $stmt, $sql)) {
|
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||||
if ($stmt->execute() === false) return false;
|
if ($stmt->execute() === false) return false;
|
||||||
if ($query->isInsert()) return $db->lastInsertId();
|
if ($query->isInsert()) return $db->lastInsertId();
|
||||||
else return $stmt->rowCount();
|
else return $stmt->rowCount();
|
||||||
} else {
|
} else {
|
||||||
$rowCount = $db->exec($sql);
|
$rowCount = $db->exec($sql);
|
||||||
if (self::is_insert($sql)) return $db->lastInsertId();
|
if ($query->isInsert()) return $db->lastInsertId();
|
||||||
else return $rowCount;
|
else return $rowCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,7 +186,7 @@ class Pdo implements IDatabase {
|
|||||||
if ($func !== null) {
|
if ($func !== null) {
|
||||||
$commited = false;
|
$commited = false;
|
||||||
try {
|
try {
|
||||||
nur_func::call($func, $this);
|
func::call($func, $this);
|
||||||
if ($commit) {
|
if ($commit) {
|
||||||
$this->commit();
|
$this->commit();
|
||||||
$commited = true;
|
$commited = true;
|
||||||
@ -222,11 +217,11 @@ class Pdo implements IDatabase {
|
|||||||
|
|
||||||
function get($query, ?array $params=null, bool $entireRow=false) {
|
function get($query, ?array $params=null, bool $entireRow=false) {
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$query = new _query_base($query, $params);
|
$query = new _pdoQuery($query, $params);
|
||||||
$stmt = null;
|
$stmt = null;
|
||||||
try {
|
try {
|
||||||
/** @var \PDOStatement $stmt */
|
/** @var \PDOStatement $stmt */
|
||||||
if ($query->useStmt($db, $stmt, $sql)) {
|
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||||
if ($stmt->execute() === false) return null;
|
if ($stmt->execute() === false) return null;
|
||||||
} else {
|
} else {
|
||||||
$stmt = $db->query($sql);
|
$stmt = $db->query($sql);
|
||||||
@ -245,22 +240,18 @@ class Pdo implements IDatabase {
|
|||||||
return $this->get($query, $params, true);
|
return $this->get($query, $params, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function all($query, ?array $params=null, $primaryKeys=null): iterable {
|
||||||
* 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 {
|
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$query = new _query_base($query, $params);
|
$query = new _pdoQuery($query, $params);
|
||||||
$stmt = null;
|
$stmt = null;
|
||||||
try {
|
try {
|
||||||
/** @var \PDOStatement $stmt */
|
/** @var \PDOStatement $stmt */
|
||||||
if ($query->useStmt($db, $stmt, $sql)) {
|
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||||
if ($stmt->execute() === false) return;
|
if ($stmt->execute() === false) return;
|
||||||
} else {
|
} else {
|
||||||
$stmt = $db->query($sql);
|
$stmt = $db->query($sql);
|
||||||
}
|
}
|
||||||
if ($primaryKeys !== null) $primaryKeys = cl::with($primaryKeys);
|
$primaryKeys = cl::withn($primaryKeys);
|
||||||
while (($row = $stmt->fetch(\PDO::FETCH_ASSOC)) !== false) {
|
while (($row = $stmt->fetch(\PDO::FETCH_ASSOC)) !== false) {
|
||||||
$this->verifixRow($row);
|
$this->verifixRow($row);
|
||||||
if ($primaryKeys !== null) {
|
if ($primaryKeys !== null) {
|
||||||
|
30
php/src/db/pdo/_pdoQuery.php
Normal file
30
php/src/db/pdo/_pdoQuery.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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
294
php/src/db/pgsql/Pgsql.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
16
php/src/db/pgsql/PgsqlException.php
Normal file
16
php/src/db/pgsql/PgsqlException.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
64
php/src/db/pgsql/PgsqlStorage.php
Normal file
64
php/src/db/pgsql/PgsqlStorage.php
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
24
php/src/db/pgsql/_pgsqlMigration.php
Normal file
24
php/src/db/pgsql/_pgsqlMigration.php
Normal 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",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
44
php/src/db/pgsql/_pgsqlQuery.php
Normal file
44
php/src/db/pgsql/_pgsqlQuery.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -3,10 +3,11 @@ namespace nulib\db\sqlite;
|
|||||||
|
|
||||||
use Generator;
|
use Generator;
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
|
use nulib\db\_private\_config;
|
||||||
use nulib\db\_private\Tvalues;
|
use nulib\db\_private\Tvalues;
|
||||||
use nulib\db\IDatabase;
|
use nulib\db\IDatabase;
|
||||||
use nulib\db\ITransactor;
|
use nulib\db\ITransactor;
|
||||||
use nulib\php\nur_func;
|
use nulib\php\func;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
use SQLite3;
|
use SQLite3;
|
||||||
use SQLite3Result;
|
use SQLite3Result;
|
||||||
@ -29,7 +30,7 @@ class Sqlite implements IDatabase {
|
|||||||
"encryption_key" => $sqlite->encryptionKey,
|
"encryption_key" => $sqlite->encryptionKey,
|
||||||
"allow_wal" => $sqlite->allowWal,
|
"allow_wal" => $sqlite->allowWal,
|
||||||
"config" => $sqlite->config,
|
"config" => $sqlite->config,
|
||||||
"migrate" => $sqlite->migration,
|
"migration" => $sqlite->migration,
|
||||||
], $params));
|
], $params));
|
||||||
} elseif (is_array($sqlite)) {
|
} elseif (is_array($sqlite)) {
|
||||||
return new static(null, cl::merge($sqlite, $params));
|
return new static(null, cl::merge($sqlite, $params));
|
||||||
@ -71,7 +72,7 @@ class Sqlite implements IDatabase {
|
|||||||
|
|
||||||
const CONFIG = null;
|
const CONFIG = null;
|
||||||
|
|
||||||
const MIGRATE = null;
|
const MIGRATION = null;
|
||||||
|
|
||||||
const params_SCHEMA = [
|
const params_SCHEMA = [
|
||||||
"file" => ["string", ""],
|
"file" => ["string", ""],
|
||||||
@ -80,7 +81,7 @@ class Sqlite implements IDatabase {
|
|||||||
"allow_wal" => ["?bool"],
|
"allow_wal" => ["?bool"],
|
||||||
"replace_config" => ["?array|callable"],
|
"replace_config" => ["?array|callable"],
|
||||||
"config" => ["?array|callable"],
|
"config" => ["?array|callable"],
|
||||||
"migrate" => ["?array|string|callable"],
|
"migration" => ["?array|string|callable"],
|
||||||
"auto_open" => ["bool", true],
|
"auto_open" => ["bool", true],
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -108,7 +109,7 @@ class Sqlite implements IDatabase {
|
|||||||
}
|
}
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
# migrations
|
# migrations
|
||||||
$this->migration = $params["migrate"] ?? static::MIGRATE;
|
$this->migration = $params["migration"] ?? static::MIGRATION;
|
||||||
#
|
#
|
||||||
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
|
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
|
||||||
$this->inTransaction = false;
|
$this->inTransaction = false;
|
||||||
@ -149,7 +150,7 @@ class Sqlite implements IDatabase {
|
|||||||
if ($this->db === null) {
|
if ($this->db === null) {
|
||||||
$this->db = new SQLite3($this->file, $this->flags, $this->encryptionKey);
|
$this->db = new SQLite3($this->file, $this->flags, $this->encryptionKey);
|
||||||
_config::with($this->config)->configure($this);
|
_config::with($this->config)->configure($this);
|
||||||
_migration::with($this->migration)->migrate($this);
|
_sqliteMigration::with($this->migration)->migrate($this);
|
||||||
$this->inTransaction = false;
|
$this->inTransaction = false;
|
||||||
}
|
}
|
||||||
return $this;
|
return $this;
|
||||||
@ -180,15 +181,10 @@ class Sqlite implements IDatabase {
|
|||||||
return $this->db()->exec($query);
|
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) {
|
function exec($query, ?array $params=null) {
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$query = new _query_base($query, $params);
|
$query = new _sqliteQuery($query, $params);
|
||||||
if ($query->useStmt($db, $stmt, $sql)) {
|
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||||
try {
|
try {
|
||||||
$result = $stmt->execute();
|
$result = $stmt->execute();
|
||||||
if ($result === false) return false;
|
if ($result === false) return false;
|
||||||
@ -201,7 +197,7 @@ class Sqlite implements IDatabase {
|
|||||||
} else {
|
} else {
|
||||||
$result = $db->exec($sql);
|
$result = $db->exec($sql);
|
||||||
if ($result === false) return false;
|
if ($result === false) return false;
|
||||||
if (self::is_insert($sql)) return $db->lastInsertRowID();
|
if ($query->isInsert()) return $db->lastInsertRowID();
|
||||||
else return $db->changes();
|
else return $db->changes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -237,7 +233,7 @@ class Sqlite implements IDatabase {
|
|||||||
if ($func !== null) {
|
if ($func !== null) {
|
||||||
$commited = false;
|
$commited = false;
|
||||||
try {
|
try {
|
||||||
nur_func::call($func, $this);
|
func::call($func, $this);
|
||||||
if ($commit) {
|
if ($commit) {
|
||||||
$this->commit();
|
$this->commit();
|
||||||
$commited = true;
|
$commited = true;
|
||||||
@ -274,8 +270,8 @@ class Sqlite implements IDatabase {
|
|||||||
|
|
||||||
function get($query, ?array $params=null, bool $entireRow=false) {
|
function get($query, ?array $params=null, bool $entireRow=false) {
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$query = new _query_base($query, $params);
|
$query = new _sqliteQuery($query, $params);
|
||||||
if ($query->useStmt($db, $stmt, $sql)) {
|
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||||
try {
|
try {
|
||||||
$result = $this->checkResult($stmt->execute());
|
$result = $this->checkResult($stmt->execute());
|
||||||
try {
|
try {
|
||||||
@ -300,7 +296,7 @@ class Sqlite implements IDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function _fetchResult(SQLite3Result $result, ?SQLite3Stmt $stmt=null, $primaryKeys=null): Generator {
|
protected function _fetchResult(SQLite3Result $result, ?SQLite3Stmt $stmt=null, $primaryKeys=null): Generator {
|
||||||
if ($primaryKeys !== null) $primaryKeys = cl::with($primaryKeys);
|
$primaryKeys = cl::withn($primaryKeys);
|
||||||
try {
|
try {
|
||||||
while (($row = $result->fetchArray(SQLITE3_ASSOC)) !== false) {
|
while (($row = $result->fetchArray(SQLITE3_ASSOC)) !== false) {
|
||||||
$this->verifixRow($row);
|
$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 {
|
function all($query, ?array $params=null, $primaryKeys=null): iterable {
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$query = new _query_base($query, $params);
|
$query = new _sqliteQuery($query, $params);
|
||||||
if ($query->useStmt($db, $stmt, $sql)) {
|
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||||
$result = $this->checkResult($stmt->execute());
|
$result = $this->checkResult($stmt->execute());
|
||||||
return $this->_fetchResult($result, $stmt, $primaryKeys);
|
return $this->_fetchResult($result, $stmt, $primaryKeys);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\sqlite;
|
namespace nulib\db\sqlite;
|
||||||
|
|
||||||
|
use nulib\cl;
|
||||||
use nulib\db\CapacitorChannel;
|
use nulib\db\CapacitorChannel;
|
||||||
use nulib\db\CapacitorStorage;
|
use nulib\db\CapacitorStorage;
|
||||||
|
|
||||||
@ -12,8 +13,7 @@ class SqliteStorage extends CapacitorStorage {
|
|||||||
$this->db = Sqlite::with($sqlite);
|
$this->db = Sqlite::with($sqlite);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var Sqlite */
|
protected Sqlite $db;
|
||||||
protected $db;
|
|
||||||
|
|
||||||
function db(): Sqlite {
|
function db(): Sqlite {
|
||||||
return $this->db;
|
return $this->db;
|
||||||
@ -23,8 +23,14 @@ class SqliteStorage extends CapacitorStorage {
|
|||||||
"id_" => "integer primary key autoincrement",
|
"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 {
|
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());
|
return self::format_sql($channel, $query->getSql());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,54 +45,33 @@ class SqliteStorage extends CapacitorStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function channelExists(string $name): bool {
|
function channelExists(string $name): bool {
|
||||||
$name = $this->db->get([
|
return null !== $this->db->get([
|
||||||
"select name from _channels",
|
"select name",
|
||||||
|
"from" => static::CHANNELS_TABLE,
|
||||||
"where" => ["name" => $name],
|
"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 {
|
protected function _afterCreate(CapacitorChannel $channel): void {
|
||||||
$db = $this->db;
|
$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
|
# ne pas créer si la table existe déjà, pour éviter d'avoir besoin d'un
|
||||||
# verrou en écriture
|
# verrou en écriture
|
||||||
$db->exec([
|
$db->exec($this->_createChannelsSql());
|
||||||
"create table if not exists",
|
|
||||||
"table" => "_channels",
|
|
||||||
"cols" => [
|
|
||||||
"name" => "varchar primary key",
|
|
||||||
"table_name" => "varchar",
|
|
||||||
"class" => "varchar",
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
if (!$this->channelExists($channel->getName())) {
|
if (!$this->channelExists($channel->getName())) {
|
||||||
# ne pas insérer si la ligne existe déjà, pour éviter d'avoir besoin d'un
|
# ne pas insérer si la ligne existe déjà, pour éviter d'avoir besoin d'un
|
||||||
# verrou en écriture
|
# verrou en écriture
|
||||||
$db->exec([
|
$db->exec($this->_addToChannelsSql($channel));
|
||||||
"insert",
|
|
||||||
"into" => "_channels",
|
|
||||||
"values" => [
|
|
||||||
"name" => $channel->getName(),
|
|
||||||
"table_name" => $channel->getTableName(),
|
|
||||||
"class" => get_class($channel),
|
|
||||||
],
|
|
||||||
"suffix" => "on conflict do nothing",
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function _beforeReset(CapacitorChannel $channel): void {
|
|
||||||
$this->db->exec([
|
|
||||||
"delete",
|
|
||||||
"from" => "_channels",
|
|
||||||
"where" => [
|
|
||||||
"name" => $channel->getName(),
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _exists(CapacitorChannel $channel): bool {
|
function _exists(CapacitorChannel $channel): bool {
|
||||||
return $this->tableExists($channel->getTableName());
|
return $this->tableExists($channel->getTableName());
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
23
php/src/db/sqlite/_sqliteMigration.php
Normal file
23
php/src/db/sqlite/_sqliteMigration.php
Normal 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,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
46
php/src/db/sqlite/_sqliteQuery.php
Normal file
46
php/src/db/sqlite/_sqliteQuery.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,7 @@ use DateTimeInterface;
|
|||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
use nulib\file\TempStream;
|
use nulib\file\TempStream;
|
||||||
use nulib\os\path;
|
use nulib\os\path;
|
||||||
use nulib\php\nur_func;
|
use nulib\php\func;
|
||||||
use nulib\php\time\DateTime;
|
use nulib\php\time\DateTime;
|
||||||
use nulib\web\http;
|
use nulib\web\http;
|
||||||
|
|
||||||
@ -35,13 +35,8 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
|
|||||||
$this->rows = $rows;
|
$this->rows = $rows;
|
||||||
$this->index = 0;
|
$this->index = 0;
|
||||||
$cookFunc = $params["cook_func"] ?? null;
|
$cookFunc = $params["cook_func"] ?? null;
|
||||||
$cookCtx = $cookArgs = null;
|
if ($cookFunc !== null) $cookFunc = func::with($cookFunc)->bind($this, true);
|
||||||
if ($cookFunc !== null) {
|
$this->cookFunc = $cookFunc;
|
||||||
nur_func::ensure_func($cookFunc, $this, $cookArgs);
|
|
||||||
$cookCtx = nur_func::_prepare($cookFunc);
|
|
||||||
}
|
|
||||||
$this->cookCtx = $cookCtx;
|
|
||||||
$this->cookArgs = $cookArgs;
|
|
||||||
$this->output = $params["output"] ?? static::OUTPUT;
|
$this->output = $params["output"] ?? static::OUTPUT;
|
||||||
$maxMemory = $params["max_memory"] ?? null;
|
$maxMemory = $params["max_memory"] ?? null;
|
||||||
$throwOnError = $params["throw_on_error"] ?? null;
|
$throwOnError = $params["throw_on_error"] ?? null;
|
||||||
@ -60,9 +55,7 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
|
|||||||
|
|
||||||
protected ?string $output;
|
protected ?string $output;
|
||||||
|
|
||||||
protected ?array $cookCtx;
|
protected ?func $cookFunc;
|
||||||
|
|
||||||
protected ?array $cookArgs;
|
|
||||||
|
|
||||||
protected function ensureHeaders(?array $row=null): void {
|
protected function ensureHeaders(?array $row=null): void {
|
||||||
if ($this->headers !== null || !$this->useHeaders) return;
|
if ($this->headers !== null || !$this->useHeaders) return;
|
||||||
@ -87,9 +80,8 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function cookRow(?array $row): ?array {
|
protected function cookRow(?array $row): ?array {
|
||||||
if ($this->cookCtx !== null) {
|
if ($this->cookFunc !== null) {
|
||||||
$args = cl::merge([$row], $this->cookArgs);
|
$row = $this->cookFunc->prependArgs([$row])->invoke();
|
||||||
$row = nur_func::_call($this->cookCtx, $args);
|
|
||||||
}
|
}
|
||||||
if ($row !== null) {
|
if ($row !== null) {
|
||||||
foreach ($row as &$col) {
|
foreach ($row as &$col) {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
namespace nulib\output;
|
namespace nulib\output;
|
||||||
|
|
||||||
use nulib\output\std\ProxyMessenger;
|
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
|
* 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 !== null && $log !== false) {
|
||||||
if ($log instanceof IMessenger) log::set_messenger($log);
|
if ($log instanceof IMessenger) log::set_messenger($log);
|
||||||
elseif (is_string($log)) log::set_messenger_class($log);
|
elseif (is_string($log)) log::set_messenger_class($log);
|
||||||
elseif (is_array($log)) {
|
else $log = func::call($log);
|
||||||
nur_func::ensure_class($log, $args);
|
|
||||||
$log = nur_func::cons($log, $args);
|
|
||||||
}
|
|
||||||
log::set_messenger($log);
|
log::set_messenger($log);
|
||||||
$msgs[] = $log;
|
$msgs[] = $log;
|
||||||
}
|
}
|
||||||
if ($console !== null && $console !== false) {
|
if ($console !== null && $console !== false) {
|
||||||
if ($console instanceof IMessenger) console::set_messenger($console);
|
if ($console instanceof IMessenger) console::set_messenger($console);
|
||||||
elseif (is_string($console)) console::set_messenger_class($console);
|
elseif (is_string($console)) console::set_messenger_class($console);
|
||||||
elseif (is_array($console)) {
|
else $console = func::call($console);
|
||||||
nur_func::ensure_class($console, $args);
|
|
||||||
$console = nur_func::cons($console, $args);
|
|
||||||
}
|
|
||||||
console::set_messenger($console);
|
console::set_messenger($console);
|
||||||
$msgs[] = $console;
|
$msgs[] = $console;
|
||||||
}
|
}
|
||||||
if ($say !== null && $say !== false) {
|
if ($say !== null && $say !== false) {
|
||||||
if ($say instanceof IMessenger) say::set_messenger($say);
|
if ($say instanceof IMessenger) say::set_messenger($say);
|
||||||
elseif (is_string($say)) say::set_messenger_class($say);
|
elseif (is_string($say)) say::set_messenger_class($say);
|
||||||
elseif (is_array($say)) {
|
else $say = func::call($say);
|
||||||
nur_func::ensure_class($say, $args);
|
|
||||||
$say = nur_func::cons($say, $args);
|
|
||||||
}
|
|
||||||
say::set_messenger($say);
|
say::set_messenger($say);
|
||||||
$msgs[] = $say;
|
$msgs[] = $say;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ namespace nulib\php\content;
|
|||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
use nulib\php\nur_func;
|
use nulib\php\func;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class c: classe outil pour gérer du contenu
|
* 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
|
# 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()
|
# ce contenu est rajouté à la suite après avoir été quoté avec self::q()
|
||||||
$func = $value;
|
$func = $value;
|
||||||
nur_func::ensure_func($func, $object_or_class, $args);
|
$values = self::q(func::call($func));
|
||||||
$values = self::q(nur_func::call($func, ...$args));
|
|
||||||
self::add_static_content($dest, $values, $key, $seq);
|
self::add_static_content($dest, $values, $key, $seq);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -83,16 +82,7 @@ class c {
|
|||||||
$arg = self::resolve($arg, $object_or_class, false);
|
$arg = self::resolve($arg, $object_or_class, false);
|
||||||
if (!$array) $arg = $arg[0];
|
if (!$array) $arg = $arg[0];
|
||||||
}; unset($arg);
|
}; unset($arg);
|
||||||
if (nur_func::is_static($func)) {
|
$value = func::with($func, $args)->bind($object_or_class, true)->invoke();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($seq) $dest[] = $value;
|
if ($seq) $dest[] = $value;
|
||||||
|
@ -3,13 +3,16 @@ namespace nulib\php;
|
|||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Generator;
|
||||||
use nulib\A;
|
use nulib\A;
|
||||||
|
use nulib\cl;
|
||||||
use nulib\cv;
|
use nulib\cv;
|
||||||
use nulib\StateException;
|
use nulib\StateException;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
use ReflectionClass;
|
use ReflectionClass;
|
||||||
use ReflectionFunction;
|
use ReflectionFunction;
|
||||||
use ReflectionMethod;
|
use ReflectionMethod;
|
||||||
|
use Traversable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class func: outils pour appeler fonctions et méthodes dynamiquement
|
* 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)
|
* la fonction (ne pas uniquement faire une vérification syntaxique)
|
||||||
*/
|
*/
|
||||||
static function verifix_function(&$func, bool $strict=true, ?string &$reason=null): bool {
|
static function verifix_function(&$func, bool $strict=true, ?string &$reason=null): bool {
|
||||||
if ($strict) {
|
if ($strict) $reason = null;
|
||||||
$msg = var_export($func, true);
|
|
||||||
$reason = null;
|
|
||||||
}
|
|
||||||
if ($func instanceof ReflectionFunction) return true;
|
if ($func instanceof ReflectionFunction) return true;
|
||||||
if (is_string($func)) {
|
if (is_string($func)) {
|
||||||
$c = false;
|
$c = false;
|
||||||
@ -82,11 +82,11 @@ class func {
|
|||||||
if ($strict) {
|
if ($strict) {
|
||||||
$reason = null;
|
$reason = null;
|
||||||
if (class_exists($f)) {
|
if (class_exists($f)) {
|
||||||
$reason = "$msg: is a class";
|
$reason = "$f: is a class";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!function_exists($f)) {
|
if (!function_exists($f)) {
|
||||||
$reason = "$msg: function not found";
|
$reason = "$f: function not found";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,10 +117,7 @@ class func {
|
|||||||
* faire une vérification syntaxique)
|
* faire une vérification syntaxique)
|
||||||
*/
|
*/
|
||||||
static function verifix_class(&$func, bool $strict=true, ?string &$reason=null): bool {
|
static function verifix_class(&$func, bool $strict=true, ?string &$reason=null): bool {
|
||||||
if ($strict) {
|
if ($strict) $reason = null;
|
||||||
$msg = var_export($func, true);
|
|
||||||
$reason = null;
|
|
||||||
}
|
|
||||||
if ($func instanceof ReflectionClass) return true;
|
if ($func instanceof ReflectionClass) return true;
|
||||||
if (is_string($func)) {
|
if (is_string($func)) {
|
||||||
$c = $func;
|
$c = $func;
|
||||||
@ -138,12 +135,10 @@ class func {
|
|||||||
if (self::_parse_static($c)) return false;
|
if (self::_parse_static($c)) return false;
|
||||||
if (self::_parse_method($c)) return false;
|
if (self::_parse_method($c)) return false;
|
||||||
if ($f !== false) return false;
|
if ($f !== false) return false;
|
||||||
if ($strict) {
|
if ($strict && !class_exists($c)) {
|
||||||
if (!class_exists($c)) {
|
$reason = "$c: class not found";
|
||||||
$reason = "$msg: class not found";
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
$func = [$c, false];
|
$func = [$c, false];
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -207,10 +202,7 @@ class func {
|
|||||||
* la méthode est liée (ne pas uniquement faire une vérification syntaxique)
|
* 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 {
|
static function verifix_static(&$func, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
|
||||||
if ($strict) {
|
if ($strict) $reason = null;
|
||||||
$msg = var_export($func, true);
|
|
||||||
$reason = null;
|
|
||||||
}
|
|
||||||
if ($func instanceof ReflectionMethod) {
|
if ($func instanceof ReflectionMethod) {
|
||||||
$bound = false;
|
$bound = false;
|
||||||
return true;
|
return true;
|
||||||
@ -265,18 +257,19 @@ class func {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ($strict) {
|
if ($strict) {
|
||||||
|
[$c, $f] = $cf;
|
||||||
$reason = null;
|
$reason = null;
|
||||||
if ($bound) {
|
if ($bound) {
|
||||||
if (!class_exists($c)) {
|
if (!class_exists($c)) {
|
||||||
$reason = "$msg: class not found";
|
$reason = "$c: class not found";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!method_exists($c, $f)) {
|
if (!method_exists($c, $f)) {
|
||||||
$reason = "$msg: method not found";
|
$reason = "$c::$f: method not found";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$reason = "$msg: not bound";
|
$reason = "$c::$f: not bound";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$func = $cf;
|
$func = $cf;
|
||||||
@ -342,10 +335,7 @@ class func {
|
|||||||
* la méthode est liée (ne pas uniquement faire une vérification syntaxique)
|
* 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 {
|
static function verifix_method(&$func, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
|
||||||
if ($strict) {
|
if ($strict) $reason = null;
|
||||||
$msg = var_export($func, true);
|
|
||||||
$reason = null;
|
|
||||||
}
|
|
||||||
if ($func instanceof ReflectionMethod) {
|
if ($func instanceof ReflectionMethod) {
|
||||||
$bound = false;
|
$bound = false;
|
||||||
return true;
|
return true;
|
||||||
@ -401,18 +391,19 @@ class func {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ($strict) {
|
if ($strict) {
|
||||||
|
[$c, $f] = $cf;
|
||||||
$reason = null;
|
$reason = null;
|
||||||
if ($bound) {
|
if ($bound) {
|
||||||
if (!is_object($c) && !class_exists($c)) {
|
if (!is_object($c) && !class_exists($c)) {
|
||||||
$reason = "$msg: class not found";
|
$reason = "$c: class not found";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!method_exists($c, $f)) {
|
if (!method_exists($c, $f)) {
|
||||||
$reason = "$msg: method not found";
|
$reason = "$c::$f: method not found";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$reason = "$msg: not bound";
|
$reason = "$c::$f: not bound";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$func = $cf;
|
$func = $cf;
|
||||||
@ -446,7 +437,7 @@ class func {
|
|||||||
return new ValueException($reason);
|
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 (!is_array($func)) {
|
||||||
if ($func instanceof Closure) {
|
if ($func instanceof Closure) {
|
||||||
return new self(self::TYPE_CLOSURE, $func, $args);
|
return new self(self::TYPE_CLOSURE, $func, $args);
|
||||||
@ -467,6 +458,12 @@ class func {
|
|||||||
} elseif (self::verifix_static($func, $strict, $bound, $reason)) {
|
} elseif (self::verifix_static($func, $strict, $bound, $reason)) {
|
||||||
return new self(self::TYPE_STATIC, $func, $args, $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);
|
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) {
|
static function call($func, ...$args) {
|
||||||
return self::with($func)->invoke($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) {
|
protected function __construct(int $type, $func, ?array $args=null, bool $bound=false, ?string $reason=null) {
|
||||||
@ -561,6 +593,27 @@ class func {
|
|||||||
|
|
||||||
protected int $maxArgs;
|
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 {
|
protected function updateReflection($reflection): void {
|
||||||
$variadic = false;
|
$variadic = false;
|
||||||
$minArgs = $maxArgs = 0;
|
$minArgs = $maxArgs = 0;
|
||||||
@ -596,11 +649,16 @@ class func {
|
|||||||
else return $this->bound && $this->object !== null;
|
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->type !== self::TYPE_METHOD) return $this;
|
||||||
|
if ($this->bound && $unlessAlreadyBound) return $this;
|
||||||
|
|
||||||
[$c, $f] = $this->func;
|
[$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->func[0] = $c = $object;
|
||||||
$this->updateReflection(new ReflectionMethod($c, $f));
|
$this->updateReflection(new ReflectionMethod($c, $f));
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ class mprop {
|
|||||||
} catch (ReflectionException $e) {
|
} catch (ReflectionException $e) {
|
||||||
return oprop::get($object, $property, $default);
|
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é */
|
/** spécifier la valeur d'une propriété */
|
||||||
@ -60,7 +60,7 @@ class mprop {
|
|||||||
} catch (ReflectionException $e) {
|
} catch (ReflectionException $e) {
|
||||||
return oprop::_set($c, $object, $property, $value);
|
return oprop::_set($c, $object, $property, $value);
|
||||||
}
|
}
|
||||||
nur_func::call([$object, $m], $value);
|
func::call([$object, $m], $value);
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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" où 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" où 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
42
php/src/ref/schema/ref_input.php
Normal file
42
php/src/ref/schema/ref_input.php
Normal 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?"]
|
||||||
|
];
|
||||||
|
}
|
@ -26,6 +26,8 @@ class ref_schema {
|
|||||||
"messages" => ["?array", null, "messages à afficher en cas d'erreur d'analyse"],
|
"messages" => ["?array", null, "messages à afficher en cas d'erreur d'analyse"],
|
||||||
"formatter_func" => ["?callable", null, "fonction qui formatte la valeur pour affichage"],
|
"formatter_func" => ["?callable", null, "fonction qui formatte la valeur pour affichage"],
|
||||||
"format" => [null, null, "format à utiliser pour l'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",
|
"" => ["array", ["scalar"], "nature du schéma",
|
||||||
"schema" => self::NATURE_METASCHEMA,
|
"schema" => self::NATURE_METASCHEMA,
|
||||||
],
|
],
|
||||||
@ -37,25 +39,48 @@ class ref_schema {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const MESSAGES = [
|
const MESSAGES = [
|
||||||
"missing" => "Vous devez spécifier cette valeur",
|
"missing" => "vous devez spécifier cette valeur",
|
||||||
"unavailable" => "Vous devez spécifier cette valeur",
|
"unavailable" => "vous devez spécifier cette valeur",
|
||||||
"null" => "Cette valeur ne doit pas être nulle",
|
"null" => "cette valeur ne doit pas être nulle",
|
||||||
"empty" => "Cette valeur ne doit pas être vide",
|
"empty" => "cette valeur ne doit pas être vide",
|
||||||
"invalid" => "Cette valeur est invalide",
|
"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 */
|
/** @var array clés supplémentaires de schéma de la nature scalaire */
|
||||||
const SCALAR_NATURE_METASCHEMA = [
|
const SCALAR_NATURE_METASCHEMA = [
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const SCALAR_PARAMS_SCHEMA = [
|
||||||
|
];
|
||||||
|
|
||||||
/** @var array clés supplémentaires de schéma de la nature associative */
|
/** @var array clés supplémentaires de schéma de la nature associative */
|
||||||
const ASSOC_NATURE_METASCHEMA = [
|
const ASSOC_NATURE_METASCHEMA = [
|
||||||
"ensure_array" => ["bool", false, "faut-il s'assurer que le tableau destination est non nul?"],
|
"ensure_array" => ["bool", null, "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_assoc" => ["bool", null, "faut-il s'assurer que le tableau destination est associatif?"],
|
||||||
"ensure_order" => ["bool", true, "faut-il s'assurer que les clés soient dans l'ordre?"],
|
"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 */
|
/** @var array clés supplémentaires de schéma de la nature liste */
|
||||||
const LIST_NATURE_METASCHEMA = [
|
const LIST_NATURE_METASCHEMA = [
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const LIST_PARAMS_SCHEMA = [
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
@ -242,6 +242,21 @@ class str {
|
|||||||
return true;
|
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
|
* ajouter $sep$prefix$text$suffix à $s si $text est non vide
|
||||||
*
|
*
|
||||||
|
@ -78,7 +78,7 @@ class ComposerFile {
|
|||||||
];
|
];
|
||||||
|
|
||||||
function selectProfile(string $profile, ComposerPmanFile $config): void {
|
function selectProfile(string $profile, ComposerPmanFile $config): void {
|
||||||
$config = $config->getProfileConfig($profile);
|
$config = $config->getProfileConfig($profile, $this->getRequires(), $this->getRequireDevs());
|
||||||
// corriger les liens
|
// corriger les liens
|
||||||
$deps = cl::merge(array_keys($config["require"]), array_keys($config["require-dev"]));
|
$deps = cl::merge(array_keys($config["require"]), array_keys($config["require-dev"]));
|
||||||
$paths = [];
|
$paths = [];
|
||||||
|
@ -4,6 +4,7 @@ namespace nulib\tools\pman;
|
|||||||
use nulib\A;
|
use nulib\A;
|
||||||
use nulib\ext\yaml;
|
use nulib\ext\yaml;
|
||||||
use nulib\os\path;
|
use nulib\os\path;
|
||||||
|
use nulib\str;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
|
|
||||||
class ComposerPmanFile {
|
class ComposerPmanFile {
|
||||||
@ -49,6 +50,8 @@ class ComposerPmanFile {
|
|||||||
$composer =& $data["composer"];
|
$composer =& $data["composer"];
|
||||||
A::ensure_array($composer);
|
A::ensure_array($composer);
|
||||||
A::ensure_array($composer["profiles"]);
|
A::ensure_array($composer["profiles"]);
|
||||||
|
A::ensure_array($composer["match_require"]);
|
||||||
|
A::ensure_array($composer["match_require-dev"]);
|
||||||
foreach ($composer["profiles"] as $profileName) {
|
foreach ($composer["profiles"] as $profileName) {
|
||||||
$profile =& $composer[$profileName];
|
$profile =& $composer[$profileName];
|
||||||
A::ensure_array($profile);
|
A::ensure_array($profile);
|
||||||
@ -61,11 +64,43 @@ class ComposerPmanFile {
|
|||||||
return $this->data;
|
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;
|
$config = $this->data["composer"][$profile] ?? null;
|
||||||
if ($config === null) {
|
if ($config === null) {
|
||||||
throw new ValueException("$profile: profil invalide");
|
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;
|
return $config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -118,7 +118,7 @@ class SqliteStorageTest extends TestCase {
|
|||||||
const COLUMN_DEFINITIONS = [
|
const COLUMN_DEFINITIONS = [
|
||||||
"a__" => "varchar",
|
"a__" => "varchar",
|
||||||
"b__" => "varchar",
|
"b__" => "varchar",
|
||||||
"b__sum_" => self::SUM_DEFINITION,
|
"b__sum_" => "sersum",
|
||||||
];
|
];
|
||||||
|
|
||||||
function getItemValues($item): ?array {
|
function getItemValues($item): ?array {
|
||||||
|
@ -11,7 +11,7 @@ class SqliteTest extends TestCase {
|
|||||||
|
|
||||||
function testMigration() {
|
function testMigration() {
|
||||||
$sqlite = new Sqlite(":memory:", [
|
$sqlite = new Sqlite(":memory:", [
|
||||||
"migrate" => [
|
"migration" => [
|
||||||
self::CREATE_PERSON,
|
self::CREATE_PERSON,
|
||||||
self::INSERT_JEPHTE,
|
self::INSERT_JEPHTE,
|
||||||
],
|
],
|
||||||
@ -49,7 +49,7 @@ class SqliteTest extends TestCase {
|
|||||||
}
|
}
|
||||||
function testInsert() {
|
function testInsert() {
|
||||||
$sqlite = new Sqlite(":memory:", [
|
$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 into mapping", "values" => ["i" => 1, "s" => "un"]]);
|
||||||
$sqlite->exec(["insert mapping", "values" => ["i" => 2, "s" => "deux"]]);
|
$sqlite->exec(["insert mapping", "values" => ["i" => 2, "s" => "deux"]]);
|
||||||
@ -78,7 +78,7 @@ class SqliteTest extends TestCase {
|
|||||||
|
|
||||||
function testSelect() {
|
function testSelect() {
|
||||||
$sqlite = new Sqlite(":memory:", [
|
$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" => "jclain1", "amount" => 1]]);
|
||||||
$sqlite->exec(["insert into user", "values" => ["name" => "jclain2", "amount" => 2]]);
|
$sqlite->exec(["insert into user", "values" => ["name" => "jclain2", "amount" => 2]]);
|
||||||
@ -130,7 +130,7 @@ class SqliteTest extends TestCase {
|
|||||||
|
|
||||||
function testSelectGroupBy() {
|
function testSelectGroupBy() {
|
||||||
$sqlite = new Sqlite(":memory:", [
|
$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" => "jclain1", "amount" => 1]]);
|
||||||
$sqlite->exec(["insert into user", "values" => ["name" => "jclain2", "amount" => 1]]);
|
$sqlite->exec(["insert into user", "values" => ["name" => "jclain2", "amount" => 1]]);
|
||||||
|
@ -6,119 +6,119 @@ use PHPUnit\Framework\TestCase;
|
|||||||
class _queryTest extends TestCase {
|
class _queryTest extends TestCase {
|
||||||
function testParseConds(): void {
|
function testParseConds(): void {
|
||||||
$sql = $params = null;
|
$sql = $params = null;
|
||||||
_query_base::parse_conds(null, $sql, $params);
|
_sqliteQuery::parse_conds(null, $sql, $params);
|
||||||
self::assertNull($sql);
|
self::assertNull($sql);
|
||||||
self::assertNull($params);
|
self::assertNull($params);
|
||||||
|
|
||||||
$sql = $params = null;
|
$sql = $params = null;
|
||||||
_query_base::parse_conds([], $sql, $params);
|
_sqliteQuery::parse_conds([], $sql, $params);
|
||||||
self::assertNull($sql);
|
self::assertNull($sql);
|
||||||
self::assertNull($params);
|
self::assertNull($params);
|
||||||
|
|
||||||
$sql = $params = null;
|
$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::assertSame(["col is null"], $sql);
|
||||||
self::assertNull($params);
|
self::assertNull($params);
|
||||||
|
|
||||||
$sql = $params = null;
|
$sql = $params = null;
|
||||||
_query_base::parse_conds(["col = 'value'"], $sql, $params);
|
_sqliteQuery::parse_conds(["col = 'value'"], $sql, $params);
|
||||||
self::assertSame(["col = 'value'"], $sql);
|
self::assertSame(["col = 'value'"], $sql);
|
||||||
self::assertNull($params);
|
self::assertNull($params);
|
||||||
|
|
||||||
$sql = $params = null;
|
$sql = $params = null;
|
||||||
_query_base::parse_conds([["col = 'value'"]], $sql, $params);
|
_sqliteQuery::parse_conds([["col = 'value'"]], $sql, $params);
|
||||||
self::assertSame(["col = 'value'"], $sql);
|
self::assertSame(["col = 'value'"], $sql);
|
||||||
self::assertNull($params);
|
self::assertNull($params);
|
||||||
|
|
||||||
$sql = $params = null;
|
$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 = :int and string = :string)"], $sql);
|
||||||
self::assertSame(["int" => 42, "string" => "value"], $params);
|
self::assertSame(["int" => 42, "string" => "value"], $params);
|
||||||
|
|
||||||
$sql = $params = null;
|
$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 = :int or string = :string)"], $sql);
|
||||||
self::assertSame(["int" => 42, "string" => "value"], $params);
|
self::assertSame(["int" => 42, "string" => "value"], $params);
|
||||||
|
|
||||||
$sql = $params = null;
|
$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 = :int and string = :string) and (int = :int2 and string = :string2))"], $sql);
|
||||||
self::assertSame(["int" => 42, "string" => "value", "int2" => 24, "string2" => "eulav"], $params);
|
self::assertSame(["int" => 42, "string" => "value", "int2" => 24, "string2" => "eulav"], $params);
|
||||||
|
|
||||||
$sql = $params = null;
|
$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(["(int is null and string <> :string)"], $sql);
|
||||||
self::assertSame(["string" => "value"], $params);
|
self::assertSame(["string" => "value"], $params);
|
||||||
|
|
||||||
$sql = $params = null;
|
$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 between :col and :col2"], $sql);
|
||||||
self::assertSame(["col" => "lower", "col2" => "upper"], $params);
|
self::assertSame(["col" => "lower", "col2" => "upper"], $params);
|
||||||
|
|
||||||
$sql = $params = null;
|
$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 in (:col)"], $sql);
|
||||||
self::assertSame(["col" => "one"], $params);
|
self::assertSame(["col" => "one"], $params);
|
||||||
|
|
||||||
$sql = $params = null;
|
$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 in (:col, :col2)"], $sql);
|
||||||
self::assertSame(["col" => "one", "col2" => "two"], $params);
|
self::assertSame(["col" => "one", "col2" => "two"], $params);
|
||||||
|
|
||||||
$sql = $params = null;
|
$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 = :col and col = :col2"], $sql);
|
||||||
self::assertSame(["col" => "one", "col2" => "two"], $params);
|
self::assertSame(["col" => "one", "col2" => "two"], $params);
|
||||||
|
|
||||||
$sql = $params = null;
|
$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 = :col or col = :col2"], $sql);
|
||||||
self::assertSame(["col" => "one", "col2" => "two"], $params);
|
self::assertSame(["col" => "one", "col2" => "two"], $params);
|
||||||
|
|
||||||
$sql = $params = null;
|
$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 <> :col and col <> :col2"], $sql);
|
||||||
self::assertSame(["col" => "one", "col2" => "two"], $params);
|
self::assertSame(["col" => "one", "col2" => "two"], $params);
|
||||||
|
|
||||||
$sql = $params = null;
|
$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 <> :col or col <> :col2"], $sql);
|
||||||
self::assertSame(["col" => "one", "col2" => "two"], $params);
|
self::assertSame(["col" => "one", "col2" => "two"], $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
function testParseValues(): void {
|
function testParseValues(): void {
|
||||||
$sql = $params = null;
|
$sql = $params = null;
|
||||||
_query_base::parse_set_values(null, $sql, $params);
|
_sqliteQuery::parse_set_values(null, $sql, $params);
|
||||||
self::assertNull($sql);
|
self::assertNull($sql);
|
||||||
self::assertNull($params);
|
self::assertNull($params);
|
||||||
|
|
||||||
$sql = $params = null;
|
$sql = $params = null;
|
||||||
_query_base::parse_set_values([], $sql, $params);
|
_sqliteQuery::parse_set_values([], $sql, $params);
|
||||||
self::assertNull($sql);
|
self::assertNull($sql);
|
||||||
self::assertNull($params);
|
self::assertNull($params);
|
||||||
|
|
||||||
$sql = $params = null;
|
$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::assertSame(["col = 'value'"], $sql);
|
||||||
self::assertNull($params);
|
self::assertNull($params);
|
||||||
|
|
||||||
$sql = $params = null;
|
$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::assertSame(["col = 'value'"], $sql);
|
||||||
self::assertNull($params);
|
self::assertNull($params);
|
||||||
|
|
||||||
$sql = $params = null;
|
$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 = :int", "string = :string"], $sql);
|
||||||
self::assertSame(["int" => 42, "string" => "value"], $params);
|
self::assertSame(["int" => 42, "string" => "value"], $params);
|
||||||
|
|
||||||
$sql = $params = null;
|
$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 = :int", "string = :string"], $sql);
|
||||||
self::assertSame(["int" => 42, "string" => "value"], $params);
|
self::assertSame(["int" => 42, "string" => "value"], $params);
|
||||||
|
|
||||||
$sql = $params = null;
|
$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 = :int", "string = :string", "int = :int2", "string = :string2"], $sql);
|
||||||
self::assertSame(["int" => 42, "string" => "value", "int2" => 24, "string2" => "eulav"], $params);
|
self::assertSame(["int" => 42, "string" => "value", "int2" => 24, "string2" => "eulav"], $params);
|
||||||
}
|
}
|
||||||
|
@ -1103,11 +1103,34 @@ namespace nulib\php {
|
|||||||
|
|
||||||
function testRebind() {
|
function testRebind() {
|
||||||
$func = func::with([C1::class, "tmethod"]);
|
$func = func::with([C1::class, "tmethod"]);
|
||||||
|
// objets du même type
|
||||||
self::assertSame(11, $func->bind(new C1(0))->invoke());
|
self::assertSame(11, $func->bind(new C1(0))->invoke());
|
||||||
self::assertSame(12, $func->bind(new C1(1))->invoke());
|
self::assertSame(12, $func->bind(new C1(1))->invoke());
|
||||||
|
// objets de type différent
|
||||||
self::assertException(ValueException::class, function() use ($func) {
|
self::assertException(ValueException::class, function() use ($func) {
|
||||||
$func->bind(new C0())->invoke();
|
$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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user