<pman>Intégration de la branche rel74-0.5.0
This commit is contained in:
		
						commit
						4037bf2042
					
				
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +1,5 @@ | |||||||
|  | /test_* | ||||||
|  | 
 | ||||||
| /.composer.lock.runphp | /.composer.lock.runphp | ||||||
| 
 | 
 | ||||||
| .~lock*# | .~lock*# | ||||||
|  | |||||||
							
								
								
									
										18
									
								
								CHANGES.md
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								CHANGES.md
									
									
									
									
									
								
							| @ -1,3 +1,21 @@ | |||||||
|  | ## Release 0.5.0p74 du 30/04/2025-04:31 | ||||||
|  | 
 | ||||||
|  | * `3ee92ef` ajout str::replace | ||||||
|  | * `3735452` améliorer le support des migrations dans les canaux | ||||||
|  | * `9767028` pas de rebind par défaut | ||||||
|  | * `cae38da` auto-migration des canaux | ||||||
|  | * `b6cc62e` ajouter les méthodes déléguées pour Capacitor | ||||||
|  | * `5e141b5` pman: ajout des clés match_require et match_require-dev | ||||||
|  | * `d4cc8bf` config pman composer | ||||||
|  | * `d241ce6` ajout PgsqlStorage | ||||||
|  | * `5c6d55e` maj ordre func | ||||||
|  | * `bab9ba8` début pgsql | ||||||
|  | * `ecd0177` migration de nur_func à func | ||||||
|  | * `bd1f901` réorganiser le code de génération sql | ||||||
|  | * `1536e09` améliorations func | ||||||
|  | 
 | ||||||
|  | ## 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 | ||||||
|  | |||||||
| @ -1 +1 @@ | |||||||
| 0.4.1 | 0.5.0 | ||||||
|  | |||||||
							
								
								
									
										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; | ||||||
| 
 | 
 | ||||||
| @ -39,6 +39,11 @@ class Capacitor implements ITransactor { | |||||||
|     return $this->getChannel()->getTableName(); |     return $this->getChannel()->getTableName(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   function getCreateSql(): string { | ||||||
|  |     $channel = $this->channel; | ||||||
|  |     return $this->storage->_getMigration($channel)->getSql(get_class($channel), $this->db()); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   /** @var CapacitorChannel[]  */ |   /** @var CapacitorChannel[]  */ | ||||||
|   protected ?array $subChannels = null; |   protected ?array $subChannels = null; | ||||||
| 
 | 
 | ||||||
| @ -87,7 +92,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 +125,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); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ use Traversable; | |||||||
| /** | /** | ||||||
|  * Class CapacitorChannel: un canal d'une instance de {@link ICapacitor} |  * Class CapacitorChannel: un canal d'une instance de {@link ICapacitor} | ||||||
|  */ |  */ | ||||||
| class CapacitorChannel { | class CapacitorChannel implements ITransactor { | ||||||
|   const NAME = null; |   const NAME = null; | ||||||
| 
 | 
 | ||||||
|   const TABLE_NAME = null; |   const TABLE_NAME = null; | ||||||
| @ -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,50 @@ 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); | ||||||
|  |     $lastMkey = 1; | ||||||
|  |     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) { | ||||||
|           $index++; |           $index++; | ||||||
|  |           if (is_array($def)) { | ||||||
|  |             # tableau: c'est une migration
 | ||||||
|  |             $mkey = null; | ||||||
|  |             $mvalues = null; | ||||||
|  |             $mdefs = $def; | ||||||
|  |             $mindex = 0; | ||||||
|  |             foreach ($mdefs as $mcol => $mdef) { | ||||||
|  |               if ($mindex === 0 && $mcol === 0) { | ||||||
|  |                 $mindex++; | ||||||
|  |                 $mkey = $mdef; | ||||||
|  |               } elseif ($mcol === $mindex) { | ||||||
|  |                 # si définition séquentielle, prendre la migration telle quelle
 | ||||||
|  |                 $mindex++; | ||||||
|  |                 $mvalues[] = $mdef; | ||||||
|  |               } elseif ($mdef) { | ||||||
|  |                 # mise à jour d'une colonne
 | ||||||
|  |                 $mvalues[] = "alter table $tableName add column $mcol $mdef"; | ||||||
|  |               } else { | ||||||
|  |                 # suppression d'une colonne
 | ||||||
|  |                 $mvalues[] = "alter table $tableName drop column $mcol"; | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |             if ($mvalues !== null) { | ||||||
|  |               if ($mkey === null) $mkey = $lastMkey++; | ||||||
|  |               $migration[$mkey] = $mvalues; | ||||||
|  |             } | ||||||
|  |           } else { | ||||||
|  |             # si définition séquentielle, seules les définitions de clé
 | ||||||
|  |             # primaires sont supportées
 | ||||||
|             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])); | ||||||
|             } |             } | ||||||
|  |           } | ||||||
|         } else { |         } else { | ||||||
|  |           # 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 +117,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 +230,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 +289,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; | ||||||
| @ -377,6 +418,42 @@ class CapacitorChannel { | |||||||
|     return $this; |     return $this; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   function willUpdate(...$transactors): ITransactor { | ||||||
|  |     return $this->capacitor->willUpdate(...$transactors); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function inTransaction(): bool { | ||||||
|  |     return $this->capacitor->inTransaction(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function beginTransaction(?callable $func=null, bool $commit=true): void { | ||||||
|  |     $this->capacitor->beginTransaction($func, $commit); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function commit(): void { | ||||||
|  |     $this->capacitor->commit(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function rollback(): void { | ||||||
|  |     $this->capacitor->rollback(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function db(): IDatabase { | ||||||
|  |     return $this->capacitor->getStorage()->db(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function exists(): bool { | ||||||
|  |     return $this->capacitor->exists(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function ensureExists(): void { | ||||||
|  |     $this->capacitor->ensureExists(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function reset(bool $recreate=false): void { | ||||||
|  |     $this->capacitor->reset($recreate); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   function charge($item, $func=null, ?array $args=null, ?array &$values=null): int { |   function charge($item, $func=null, ?array $args=null, ?array &$values=null): int { | ||||||
|     return $this->capacitor->charge($item, $func, $args, $values); |     return $this->capacitor->charge($item, $func, $args, $values); | ||||||
|   } |   } | ||||||
| @ -404,4 +481,8 @@ class CapacitorChannel { | |||||||
|   function delete($filter, $func=null, ?array $args=null): int { |   function delete($filter, $func=null, ?array $args=null): int { | ||||||
|     return $this->capacitor->delete($filter, $func, $args); |     return $this->capacitor->delete($filter, $func, $args); | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   function close(): void { | ||||||
|  |     $this->capacitor->close(); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -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,28 @@ 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"; | ||||||
|  | 
 | ||||||
|  |   protected static function sercol($def): string { | ||||||
|  |     if (!is_string($def)) $def = strval($def); | ||||||
|  |     switch ($def) { | ||||||
|  |     case "serdata": $def = static::SERDATA_DEFINITION; break; | ||||||
|  |     case "sersum": $def = static::SERSUM_DEFINITION; break; | ||||||
|  |     case "serts": $def = static::SERTS_DEFINITION; break; | ||||||
|  |     } | ||||||
|  |     return $def; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   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; | ||||||
| @ -58,14 +73,36 @@ abstract class CapacitorStorage { | |||||||
|     foreach ($tmp as $col => $def) { |     foreach ($tmp as $col => $def) { | ||||||
|       if ($col === $index) { |       if ($col === $index) { | ||||||
|         $index++; |         $index++; | ||||||
|         $constraints[] = $def; |         if (is_array($def)) { | ||||||
|  |           if (!$ignoreMigrations) { | ||||||
|  |             $mdefs = $def; | ||||||
|  |             $mindex = 0; | ||||||
|  |             foreach ($mdefs as $mcol => $mdef) { | ||||||
|  |               if ($mcol === $mindex) { | ||||||
|  |                 $mindex++; | ||||||
|               } else { |               } else { | ||||||
|         $definitions[$col] = $def; |                 if ($mdef) { | ||||||
|  |                   $definitions[$mcol] = self::sercol($mdef); | ||||||
|  |                 } else { | ||||||
|  |                   unset($definitions[$mcol]); | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } else { | ||||||
|  |           $constraints[] = $def; | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         $definitions[$col] = self::sercol($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,46 +165,97 @@ 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), | ||||||
|     ]; |     ]; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   protected static function format_sql(CapacitorChannel $channel, string $sql): string { |   abstract protected function tableExists(string $tableName): bool; | ||||||
|     $class = get_class($channel); |  | ||||||
|     return <<<EOT |  | ||||||
| -- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 |  | ||||||
| -- autogénéré à partir de $class |  | ||||||
| 
 | 
 | ||||||
| $sql; |   const METADATA_TABLE = "_metadata"; | ||||||
|  |   const METADATA_COLS = [ | ||||||
|  |     "name" => "varchar not null primary key", | ||||||
|  |     "value" => "varchar", | ||||||
|  |   ]; | ||||||
| 
 | 
 | ||||||
| EOT; |   protected function _prepareMetadata(): void { | ||||||
|  |     if (!$this->tableExists(static::METADATA_TABLE)) { | ||||||
|  |       $db = $this->db(); | ||||||
|  |       $db->exec([ | ||||||
|  |         "drop table if exists", | ||||||
|  |         "table" => self::CHANNELS_TABLE, | ||||||
|  |       ]); | ||||||
|  |       $db->exec([ | ||||||
|  |         "drop table if exists", | ||||||
|  |         "table" => _migration::MIGRATION_TABLE, | ||||||
|  |       ]); | ||||||
|  |       $db->exec([ | ||||||
|  |         "create table", | ||||||
|  |         "table" => static::METADATA_TABLE, | ||||||
|  |         "cols" => static::METADATA_COLS, | ||||||
|  |       ]); | ||||||
|  |       $db->exec([ | ||||||
|  |         "insert", | ||||||
|  |         "into" => static::METADATA_TABLE, | ||||||
|  |         "values" => [ | ||||||
|  |           "name" => "version", | ||||||
|  |           "value" => "1", | ||||||
|  |         ], | ||||||
|  |       ]); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   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 not null 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->_prepareMetadata(); | ||||||
|  |       $this->_getMigration($channel)->migrate($this->db()); | ||||||
|       $this->_afterCreate($channel); |       $this->_afterCreate($channel); | ||||||
|       $channel->setCreated(); |       $channel->setCreated(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** tester si le canal spécifié existe */ |   /** tester si le canal spécifié existe */ | ||||||
|   abstract function _exists(CapacitorChannel $channel): bool; |   function _exists(CapacitorChannel $channel): bool { | ||||||
|  |     return $this->tableExists($channel->getTableName()); | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   function exists(?string $channel): bool { |   function exists(?string $channel): bool { | ||||||
|     return $this->_exists($this->getChannel($channel)); |     return $this->_exists($this->getChannel($channel)); | ||||||
| @ -183,12 +271,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 +334,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 +360,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 +375,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 +392,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) | ||||||
|  |         ->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 +609,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); | ||||||
|     $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 +626,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 +677,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); | ||||||
|     $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 +693,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", | ||||||
|  | |||||||
| @ -2,6 +2,9 @@ | |||||||
| namespace nulib\db; | namespace nulib\db; | ||||||
| 
 | 
 | ||||||
| interface IDatabase extends ITransactor { | interface IDatabase extends ITransactor { | ||||||
|  |   /** obtenir la requête SQL correspondant à $query */ | ||||||
|  |   function getSql($query, ?array $params=null): string; | ||||||
|  | 
 | ||||||
|   /** |   /** | ||||||
|    * - si c'est un insert, retourner l'identifiant autogénéré de la ligne |    * - si c'est un insert, retourner l'identifiant autogénéré de la ligne | ||||||
|    * - sinon retourner le nombre de lignes modifiées en cas de succès, ou false |    * - sinon retourner le nombre de lignes modifiées en cas de succès, ou false | ||||||
| @ -15,5 +18,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); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,259 +1,59 @@ | |||||||
| <?php | <?php | ||||||
| namespace nulib\db\_private; | namespace nulib\db\_private; | ||||||
| 
 | 
 | ||||||
| use nulib\cl; |  | ||||||
| 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)->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,24 @@ | |||||||
| <?php | <?php | ||||||
| namespace nulib\db\_private; | namespace nulib\db\_private; | ||||||
| 
 | 
 | ||||||
| class _generic { | use nulib\str; | ||||||
|  | 
 | ||||||
|  | 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 { | ||||||
|  |     $sql = ""; | ||||||
|  |     foreach ($query as $value) { | ||||||
|  |       if ($sql && !str::ends_with(" ", $sql) && !str::starts_with(" ", $value)) { | ||||||
|  |         $sql .= " "; | ||||||
|  |       } | ||||||
|  |       $sql .= $value; | ||||||
|  |     } | ||||||
|  |     return $sql; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -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); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										99
									
								
								php/src/db/_private/_migration.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								php/src/db/_private/_migration.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,99 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\db\_private; | ||||||
|  | 
 | ||||||
|  | use nulib\cl; | ||||||
|  | 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 (func::is_callable($migration)) { | ||||||
|  |         func::with($migration)->bind($this)->invoke([$db, $name]); | ||||||
|  |       } else { | ||||||
|  |         foreach (cl::with($migration) as $query) { | ||||||
|  |           $db->exec($query); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       $this->setMigrated($name, true); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected static function sql_prefix(?string $source=null): string { | ||||||
|  |     $prefix = "-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8\n"; | ||||||
|  |     if ($source !== null) $prefix .= "-- autogénéré à partir de $source\n"; | ||||||
|  |     return $prefix; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function getSql(?string $source=null, ?IDatabase $db=null): string { | ||||||
|  |     $db = ($this->db ??= $db); | ||||||
|  |     $lines = [self::sql_prefix($source)]; | ||||||
|  |     foreach ($this->migrations as $name => $migration) { | ||||||
|  |       $lines[] = "-- $name"; | ||||||
|  |       if (func::is_callable($migration)) { | ||||||
|  |         $lines[] = "-- <fonction PHP>"; | ||||||
|  |       } else { | ||||||
|  |         foreach (cl::with($migration) as $query) { | ||||||
|  |           $sql = $db->getSql($query); | ||||||
|  |           $lines[] = "$sql;"; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       $lines[] = ""; | ||||||
|  |     } | ||||||
|  |     return implode("\n", $lines); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -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,21 +23,40 @@ class MysqlStorage extends CapacitorStorage { | |||||||
|     "id_" => "integer primary key auto_increment", |     "id_" => "integer primary key auto_increment", | ||||||
|   ]; |   ]; | ||||||
| 
 | 
 | ||||||
|   function _getCreateSql(CapacitorChannel $channel): string { |   protected function tableExists(string $tableName): bool { | ||||||
|     $query = new _query_base($this->_createSql($channel)); |  | ||||||
|     return self::format_sql($channel, $query->getSql()); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   function _exists(CapacitorChannel $channel): bool { |  | ||||||
|     $db = $this->db; |     $db = $this->db; | ||||||
|     $tableName = $db->get([ |     $found = $db->get([ | ||||||
|       "select table_name from information_schema.tables", |       "select table_name from information_schema.tables", | ||||||
|       "where" => [ |       "where" => [ | ||||||
|         "table_schema" => $db->getDbname(), |         "table_schema" => $db->getDbname(), | ||||||
|         "table_name" => $channel->getTableName(), |         "table_name" => $tableName, | ||||||
|       ], |       ], | ||||||
|     ]); |     ]); | ||||||
|     return $tableName !== null; |     return $found !== null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const METADATA_COLS = [ | ||||||
|  |     "name" => "varchar(64) not null primary key", | ||||||
|  |     "value" => "varchar(255)", | ||||||
|  |   ]; | ||||||
|  | 
 | ||||||
|  |   function _getMigration(CapacitorChannel $channel): _mysqlMigration { | ||||||
|  |     $migrations = cl::merge([ | ||||||
|  |       "0init" => [$this->_createSql($channel)], | ||||||
|  |     ], $channel->getMigration()); | ||||||
|  |     return new _mysqlMigration($migrations, $channel->getName()); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const CHANNELS_COLS = [ | ||||||
|  |     "name" => "varchar(255) not null 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 close(): void { |   function close(): void { | ||||||
|  | |||||||
							
								
								
									
										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]; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,12 +1,12 @@ | |||||||
| <?php | <?php | ||||||
| namespace nulib\db\pdo; | namespace nulib\db\pdo; | ||||||
| 
 | 
 | ||||||
| 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 +21,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 +49,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 +62,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 +93,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 +104,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; | ||||||
| @ -114,13 +114,17 @@ class Pdo implements IDatabase { | |||||||
| 
 | 
 | ||||||
|   protected ?\PDO $db = null; |   protected ?\PDO $db = null; | ||||||
| 
 | 
 | ||||||
|  |   function getSql($query, ?array $params=null): string { | ||||||
|  |     $query = new _pdoQuery($query, $params); | ||||||
|  |     return $query->getSql(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   function open(): self { |   function open(): self { | ||||||
|     if ($this->db === null) { |     if ($this->db === null) { | ||||||
|       $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)->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 +147,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 +190,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 +221,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 +244,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; |  | ||||||
| } |  | ||||||
							
								
								
									
										299
									
								
								php/src/db/pgsql/Pgsql.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										299
									
								
								php/src/db/pgsql/Pgsql.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,299 @@ | |||||||
|  | <?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 getSql($query, ?array $params=null): string { | ||||||
|  |     $query = new _pgsqlQuery($query, $params); | ||||||
|  |     return $query->getSql(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   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)->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); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										15
									
								
								php/src/db/pgsql/PgsqlException.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								php/src/db/pgsql/PgsqlException.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\db\pgsql; | ||||||
|  | 
 | ||||||
|  | use Exception; | ||||||
|  | use RuntimeException; | ||||||
|  | 
 | ||||||
|  | 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); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										60
									
								
								php/src/db/pgsql/PgsqlStorage.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								php/src/db/pgsql/PgsqlStorage.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,60 @@ | |||||||
|  | <?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", | ||||||
|  |   ]; | ||||||
|  | 
 | ||||||
|  |   protected function tableExists(string $tableName): bool { | ||||||
|  |     if (($index = strpos($tableName, ".")) !== false) { | ||||||
|  |       $schemaName = substr($tableName, 0, $index); | ||||||
|  |       $tableName = substr($tableName, $index + 1); | ||||||
|  |     } else { | ||||||
|  |       $schemaName = "public"; | ||||||
|  |     } | ||||||
|  |     $found = $this->db->get([ | ||||||
|  |       "select tablename from pg_tables", | ||||||
|  |       "where" => [ | ||||||
|  |         "schemaname" => $schemaName, | ||||||
|  |         "tablename" => $tableName, | ||||||
|  |       ], | ||||||
|  |     ]); | ||||||
|  |     return $found !== null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function _getMigration(CapacitorChannel $channel): _pgsqlMigration { | ||||||
|  |     $migrations = cl::merge([ | ||||||
|  |       "0init" => [$this->_createSql($channel)], | ||||||
|  |     ], $channel->getMigration()); | ||||||
|  |     return new _pgsqlMigration($migrations, $channel->getName()); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function _addToChannelsSql(CapacitorChannel $channel): array { | ||||||
|  |     return cl::merge(parent::_addToChannelsSql($channel), [ | ||||||
|  |       "suffix" => "on conflict (name) do nothing", | ||||||
|  |     ]); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   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; | ||||||
| @ -145,11 +146,16 @@ class Sqlite implements IDatabase { | |||||||
| 
 | 
 | ||||||
|   protected bool $inTransaction; |   protected bool $inTransaction; | ||||||
| 
 | 
 | ||||||
|  |   function getSql($query, ?array $params=null): string { | ||||||
|  |     $query = new _sqliteQuery($query, $params); | ||||||
|  |     return $query->getSql(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   function open(): self { |   function open(): self { | ||||||
|     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 +186,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 +202,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 +238,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 +275,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 +301,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 +318,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,74 +23,51 @@ class SqliteStorage extends CapacitorStorage { | |||||||
|     "id_" => "integer primary key autoincrement", |     "id_" => "integer primary key autoincrement", | ||||||
|   ]; |   ]; | ||||||
| 
 | 
 | ||||||
|   function _getCreateSql(CapacitorChannel $channel): string { |   protected function tableExists(string $tableName): bool { | ||||||
|     $query = new _query_base($this->_createSql($channel)); |     $found = $this->db->get([ | ||||||
|     return self::format_sql($channel, $query->getSql()); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   function tableExists(string $tableName): bool { |  | ||||||
|     $name = $this->db->get([ |  | ||||||
|       # depuis la version 3.33.0 le nom officiel de la table est sqlite_schema,
 |       # depuis la version 3.33.0 le nom officiel de la table est sqlite_schema,
 | ||||||
|       # mais le nom sqlite_master est toujours valable pour le moment
 |       # mais le nom sqlite_master est toujours valable pour le moment
 | ||||||
|       "select name from sqlite_master ", |       "select name from sqlite_master ", | ||||||
|       "where" => ["name" => $tableName], |       "where" => ["name" => $tableName], | ||||||
|     ]); |     ]); | ||||||
|     return $name !== null; |     return $found !== null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function _getMigration(CapacitorChannel $channel): _sqliteMigration { | ||||||
|  |     $migrations = cl::merge([ | ||||||
|  |       "0init" => [$this->_createSql($channel)], | ||||||
|  |     ], $channel->getMigration()); | ||||||
|  |     return new _sqliteMigration($migrations, $channel->getName()); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   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 { |  | ||||||
|     return $this->tableExists($channel->getTableName()); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   function close(): void { |   function close(): void { | ||||||
|     $this->db->close(); |     $this->db->close(); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -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, | ||||||
|  |       ], | ||||||
|  |     ]); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								php/src/db/sqlite/_sqliteQuery.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								php/src/db/sqlite/_sqliteQuery.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\db\sqlite; | ||||||
|  | 
 | ||||||
|  | use nulib\db\_private\_base; | ||||||
|  | use nulib\db\_private\Tbindings; | ||||||
|  | use nulib\output\msg; | ||||||
|  | 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); | ||||||
|     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)->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; | ||||||
|  | |||||||
| @ -4,12 +4,14 @@ namespace nulib\php; | |||||||
| use Closure; | use Closure; | ||||||
| use Exception; | use Exception; | ||||||
| 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 +60,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 +81,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 +116,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 +134,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 +201,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 +256,21 @@ 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; | ||||||
|         } |         } | ||||||
|  |         $method = new ReflectionMethod($c, $f); | ||||||
|  |         if (!$method->isStatic()) return false; | ||||||
|       } else { |       } else { | ||||||
|         $reason = "$msg: not bound"; |         $reason = "$c::$f: not bound"; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     $func = $cf; |     $func = $cf; | ||||||
| @ -342,10 +336,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 +392,21 @@ 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; | ||||||
|         } |         } | ||||||
|  |         $method = new ReflectionMethod($c, $f); | ||||||
|  |         if ($method->isStatic()) return false; | ||||||
|       } else { |       } else { | ||||||
|         $reason = "$msg: not bound"; |         $reason = "$c::$f: not bound"; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     $func = $cf; |     $func = $cf; | ||||||
| @ -446,7 +440,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 +461,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 +487,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 +596,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 +652,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 $rebind=false, bool $replace=false): self { | ||||||
|     if ($this->type !== self::TYPE_METHOD) return $this; |     if ($this->type !== self::TYPE_METHOD) return $this; | ||||||
|  |     if (!$rebind && $this->isBound()) 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 | ||||||
|    * |    * | ||||||
| @ -253,6 +268,21 @@ class str { | |||||||
|     $s .= $prefix.$text.$suffix; |     $s .= $prefix.$text.$suffix; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /** | ||||||
|  |    * dans $s, faire les remplacements $key => $value du tableau $replaces | ||||||
|  |    * | ||||||
|  |    * si $verifix_order, le tableau est réordonné par taille de chaine source | ||||||
|  |    */ | ||||||
|  |   static final function replace(?string $s, ?array $replaces, bool $verifix_order=true): ?string { | ||||||
|  |     if ($s === null || $replaces === null) return $s; | ||||||
|  |     if ($verifix_order) { | ||||||
|  |       uksort($replaces, function ($a, $b) { | ||||||
|  |         return -cv::compare(strlen($a), strlen($b)); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |     return str_replace(array_keys($replaces), array_values($replaces), $s); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   /** si $text est non vide, ajouter $prefix$text$suffix à $s en séparant la valeur avec un espace */ |   /** si $text est non vide, ajouter $prefix$text$suffix à $s en séparant la valeur avec un espace */ | ||||||
|   static final function add(?string &$s, ?string $text, ?string $prefix=null, ?string $suffix=null): void { |   static final function add(?string &$s, ?string $text, ?string $prefix=null, ?string $suffix=null): void { | ||||||
|     self::addsep($s, " ", $text, $prefix, $suffix); |     self::addsep($s, " ", $text, $prefix, $suffix); | ||||||
|  | |||||||
| @ -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() { |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
							
								
								
									
										75
									
								
								php/tests/db/sqlite/ChannelMigrationTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								php/tests/db/sqlite/ChannelMigrationTest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,75 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\db\sqlite; | ||||||
|  | 
 | ||||||
|  | use nulib\db\Capacitor; | ||||||
|  | use nulib\db\sqlite\impl\MyChannel; | ||||||
|  | use nulib\db\sqlite\impl\MyChannelV2; | ||||||
|  | use nulib\db\sqlite\impl\MyChannelV3; | ||||||
|  | use nulib\output\msg; | ||||||
|  | use nulib\output\std\StdMessenger; | ||||||
|  | use nulib\php\time\DateTime; | ||||||
|  | use nulib\tests\TestCase; | ||||||
|  | 
 | ||||||
|  | class ChannelMigrationTest extends TestCase { | ||||||
|  |   static function setUpBeforeClass(): void { | ||||||
|  |     parent::setUpBeforeClass(); | ||||||
|  |     msg::set_messenger_class(StdMessenger::class); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function addData(MyChannel $channel, array $data): void { | ||||||
|  |     foreach ($data as [$name, $value, $dateCre, $dateMod, $age]) { | ||||||
|  |       $channel->charge([ | ||||||
|  |         "name" => $name, | ||||||
|  |         "value" => $value, | ||||||
|  |         "date_cre" => $dateCre, | ||||||
|  |         "date_mod" => $dateMod, | ||||||
|  |         "age" => $age, | ||||||
|  |       ]); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function testMigration() { | ||||||
|  |     $storage = new SqliteStorage(__DIR__.'/capacitor.db'); | ||||||
|  |     $data = [ | ||||||
|  |       ["first", "premier", new DateTime(), new DateTime(), 15], | ||||||
|  |       ["second", "deuxieme", new DateTime(), new DateTime(), 15], | ||||||
|  |     ]; | ||||||
|  | 
 | ||||||
|  |     new Capacitor($storage, $channel = new MyChannel()); | ||||||
|  |     $channel->reset(true); | ||||||
|  |     $this->addData($channel, $data); | ||||||
|  | 
 | ||||||
|  |     new Capacitor($storage, $channel = new MyChannelV2()); | ||||||
|  |     $this->addData($channel, $data); | ||||||
|  | 
 | ||||||
|  |     new Capacitor($storage, $channel = new MyChannelV3()); | ||||||
|  |     $this->addData($channel, $data); | ||||||
|  | 
 | ||||||
|  |     $sql = $channel->getCapacitor()->getCreateSql(); | ||||||
|  |     $class = MyChannelV3::class; | ||||||
|  |     $expected = <<<EOT | ||||||
|  | -- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 | ||||||
|  | -- autogénéré à partir de $class | ||||||
|  | 
 | ||||||
|  | -- 0init | ||||||
|  | create table  if not exists my ( | ||||||
|  |   id_ integer primary key autoincrement | ||||||
|  | , name varchar | ||||||
|  | , value varchar | ||||||
|  | , item__ mediumtext | ||||||
|  | , item__sum_ varchar(40) | ||||||
|  | , created_ datetime | ||||||
|  | , modified_ datetime | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | -- dates | ||||||
|  | alter table my add column date_cre datetime; | ||||||
|  | alter table my add column date_mod datetime; | ||||||
|  | 
 | ||||||
|  | -- infos | ||||||
|  | alter table my add column age integer; | ||||||
|  | 
 | ||||||
|  | EOT; | ||||||
|  |     self::assertSame($expected, $sql); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -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); | ||||||
|   } |   } | ||||||
|  | |||||||
							
								
								
									
										33
									
								
								php/tests/db/sqlite/impl/MyChannel.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								php/tests/db/sqlite/impl/MyChannel.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\db\sqlite\impl; | ||||||
|  | 
 | ||||||
|  | use nulib\db\CapacitorChannel; | ||||||
|  | 
 | ||||||
|  | class MyChannel extends CapacitorChannel { | ||||||
|  |   const NAME = "my"; | ||||||
|  |   const TABLE_NAME = "my"; | ||||||
|  |   const COLUMN_DEFINITIONS = [ | ||||||
|  |     "name" => "varchar not null primary key", | ||||||
|  |     "value" => "varchar", | ||||||
|  |   ]; | ||||||
|  | 
 | ||||||
|  |   const VERSION = 1; | ||||||
|  | 
 | ||||||
|  |   function __construct() { | ||||||
|  |     parent::__construct(); | ||||||
|  |     $this->version = static::VERSION; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected int $version; | ||||||
|  | 
 | ||||||
|  |   function getItemValues($item): ?array { | ||||||
|  | 
 | ||||||
|  |     return [ | ||||||
|  |       "name" => "{$item["name"]}$this->version", | ||||||
|  |       "value" => "{$item["value"]} v$this->version", | ||||||
|  |       "date_cre" => $item["date_cre"] ?? null, | ||||||
|  |       "date_mod" => $item["date_mod"] ?? null, | ||||||
|  |       "age" => $item["age"] ?? null, | ||||||
|  |     ]; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								php/tests/db/sqlite/impl/MyChannelV2.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								php/tests/db/sqlite/impl/MyChannelV2.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\db\sqlite\impl; | ||||||
|  | 
 | ||||||
|  | class MyChannelV2 extends MyChannel { | ||||||
|  |   const VERSION = 2; | ||||||
|  |   const COLUMN_DEFINITIONS = [ | ||||||
|  |     "name" => "varchar", | ||||||
|  |     "value" => "varchar", | ||||||
|  |     ["dates", | ||||||
|  |       "date_cre" => "datetime", | ||||||
|  |       "date_mod" => "datetime", | ||||||
|  |     ], | ||||||
|  |   ]; | ||||||
|  | } | ||||||
							
								
								
									
										17
									
								
								php/tests/db/sqlite/impl/MyChannelV3.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								php/tests/db/sqlite/impl/MyChannelV3.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\db\sqlite\impl; | ||||||
|  | 
 | ||||||
|  | class MyChannelV3 extends MyChannel { | ||||||
|  |   const VERSION = 3; | ||||||
|  |   const COLUMN_DEFINITIONS = [ | ||||||
|  |     "name" => "varchar", | ||||||
|  |     "value" => "varchar", | ||||||
|  |     ["dates", | ||||||
|  |       "date_cre" => "datetime", | ||||||
|  |       "date_mod" => "datetime", | ||||||
|  |     ], | ||||||
|  |     ["infos", | ||||||
|  |       "age" => "integer", | ||||||
|  |     ], | ||||||
|  |   ]; | ||||||
|  | } | ||||||
| @ -562,7 +562,7 @@ namespace nulib\php { | |||||||
|         true, true, [SC::class, "tstatic"], |         true, true, [SC::class, "tstatic"], | ||||||
|       ], |       ], | ||||||
|       [[SC::class, "tmethod"], |       [[SC::class, "tmethod"], | ||||||
|         true, true, [SC::class, "tmethod"], |         false, true, [SC::class, "tmethod"], | ||||||
|         true, true, [SC::class, "tmethod"], |         true, true, [SC::class, "tmethod"], | ||||||
|       ], |       ], | ||||||
|       [[SC::class, "->tmethod"], |       [[SC::class, "->tmethod"], | ||||||
| @ -1102,12 +1102,45 @@ namespace nulib\php { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     function testRebind() { |     function testRebind() { | ||||||
|  |       # bind if not already bound
 | ||||||
|       $func = func::with([C1::class, "tmethod"]); |       $func = func::with([C1::class, "tmethod"]); | ||||||
|  |       // bind
 | ||||||
|       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()); |       // pas de bind, puis que déjà bound
 | ||||||
|  |       self::assertSame(11, $func->bind(new C1(1))->invoke()); | ||||||
|  |       // même si l'objet est de type différent, pas de bind
 | ||||||
|  |       self::assertSame(11, $func->bind(new C0())->invoke()); | ||||||
|  | 
 | ||||||
|  |       # force rebind
 | ||||||
|  |       $func = func::with([C1::class, "tmethod"]); | ||||||
|  |       // objets du même type
 | ||||||
|  |       self::assertSame(11, $func->bind(new C1(0), true)->invoke()); | ||||||
|  |       self::assertSame(12, $func->bind(new C1(1), true)->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(), true)->invoke(); | ||||||
|       }); |       }); | ||||||
|  |       self::assertSame(11, $func->bind(new C0(), true, 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; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -5,7 +5,22 @@ namespace nulib; | |||||||
| use nulib\tests\TestCase; | use nulib\tests\TestCase; | ||||||
| 
 | 
 | ||||||
| class strTest extends TestCase { | class strTest extends TestCase { | ||||||
|   function testSplit_tokens() { |   function test_replace() { | ||||||
|  |     self::assertSame("premier deuxieme", str::replace("first second", [ | ||||||
|  |       "first" => "premier", | ||||||
|  |       "second" => "deuxieme", | ||||||
|  |     ])); | ||||||
|  |     self::assertSame("avant OK", str::replace("prefix prefixsuffix", [ | ||||||
|  |       "prefix" => "avant", | ||||||
|  |       "prefixsuffix" => "OK", | ||||||
|  |     ])); | ||||||
|  |     self::assertSame("avant avantsuffix", str::replace("prefix prefixsuffix", [ | ||||||
|  |       "prefix" => "avant", | ||||||
|  |       "prefixsuffix" => "OK", | ||||||
|  |     ], false)); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function test_split_tokens() { | ||||||
|     self::assertNull(str::split_tokens(null)); |     self::assertNull(str::split_tokens(null)); | ||||||
|     self::assertSame([], str::split_tokens("")); |     self::assertSame([], str::split_tokens("")); | ||||||
|     self::assertSame(["token"], str::split_tokens("token")); |     self::assertSame(["token"], str::split_tokens("token")); | ||||||
| @ -13,7 +28,7 @@ class strTest extends TestCase { | |||||||
|     self::assertSame(["t", "u", "v", "w"], str::split_tokens("\nt\n\nu\r\nv\rw")); |     self::assertSame(["t", "u", "v", "w"], str::split_tokens("\nt\n\nu\r\nv\rw")); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function testCamel2us() { |   function test_camel2us() { | ||||||
|     self::assertSame("a", str::camel2us("a")); |     self::assertSame("a", str::camel2us("a")); | ||||||
|     self::assertSame("aa", str::camel2us("aa")); |     self::assertSame("aa", str::camel2us("aa")); | ||||||
|     self::assertSame("aaa", str::camel2us("aaa")); |     self::assertSame("aaa", str::camel2us("aaa")); | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user