Compare commits
	
		
			26 Commits
		
	
	
		
			33fab8496a
			...
			2af9b0b8f0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 2af9b0b8f0 | |||
| b6cc62e010 | |||
| b4e28ade02 | |||
| d63a0cb704 | |||
| 91beea9c29 | |||
| 0e9be5f221 | |||
| 5e141b575e | |||
| 8ee13a85c0 | |||
| 2af417a193 | |||
| d4cc8bfa42 | |||
| ca129dfda4 | |||
| 2a50167241 | |||
| 146461a184 | |||
| 2a46c12e08 | |||
| d241ce6561 | |||
| 86136e75a5 | |||
| 64c872cf3f | |||
| ebbd9e06c0 | |||
| 5c6d55ed46 | |||
| bab9ba81fe | |||
| ecd01777c1 | |||
| bd1f901b70 | |||
| 1536e091fb | |||
| a8d55d329a | |||
| 60ab13ff84 | |||
| f2614385fe | 
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -1,3 +1,5 @@
 | 
			
		||||
/test_*
 | 
			
		||||
 | 
			
		||||
/.composer.lock.runphp
 | 
			
		||||
 | 
			
		||||
.~lock*#
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										59
									
								
								bin/pman
									
									
									
									
									
								
							
							
						
						
									
										59
									
								
								bin/pman
									
									
									
									
									
								
							@ -91,6 +91,48 @@ function init_config_action() {
 | 
			
		||||
    _push_branches
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function _init_composer() {
 | 
			
		||||
    if [ ! -f .composer.pman.yml -o -n "$ForceCreate" ]; then
 | 
			
		||||
        ac_set_tmpfile config
 | 
			
		||||
        cat >"$config" <<EOF
 | 
			
		||||
# -*- coding: utf-8 mode: yaml -*- vim:sw=2:sts=2:et:ai:si:sta:fenc=utf-8
 | 
			
		||||
 | 
			
		||||
composer:
 | 
			
		||||
  match_prefix:
 | 
			
		||||
  match_prefix-dev:
 | 
			
		||||
  profiles: [ dev, dist ]
 | 
			
		||||
  dev:
 | 
			
		||||
    link: true
 | 
			
		||||
    require:
 | 
			
		||||
    reqire-dev:
 | 
			
		||||
  dist:
 | 
			
		||||
    link: false
 | 
			
		||||
    require:
 | 
			
		||||
    reqire-dev:
 | 
			
		||||
EOF
 | 
			
		||||
        "${EDITOR:-nano}" "$config"
 | 
			
		||||
        [ -s "$config" ] || return 1
 | 
			
		||||
 | 
			
		||||
        cp "$config" .composer.pman.yml
 | 
			
		||||
        git add .composer.pman.yml
 | 
			
		||||
    fi
 | 
			
		||||
    return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function init_composer_action() {
 | 
			
		||||
    local -a push_branches; local config
 | 
			
		||||
 | 
			
		||||
    [ -f .composer.pman.yml -a -z "$ForceCreate" ] && die "La configuration pman composer a déjà été initialisée"
 | 
			
		||||
 | 
			
		||||
    resolve_should_push
 | 
			
		||||
 | 
			
		||||
    _init_composer || exit_with ewarn "Initialisation de la configuration annulée"
 | 
			
		||||
    git commit -m "configuration pman composer"
 | 
			
		||||
    push_branches+=("$CurrentBranch")
 | 
			
		||||
 | 
			
		||||
    _push_branches
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function _ensure_main_branch() {
 | 
			
		||||
    [ -n "$MAIN" ] || die "La branche MAIN n'a pas été définie"
 | 
			
		||||
    [ -n "$MainBranch" ] || die "$MAIN: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
 | 
			
		||||
@ -108,7 +150,7 @@ $MAIN: une branche du même nom existe dans l'origine
 | 
			
		||||
 | 
			
		||||
function _ensure_develop_branch() {
 | 
			
		||||
    [ -n "$DEVELOP" ] || die "La branche DEVELOP n'a pas été définie"
 | 
			
		||||
    [ -n "$DevelopBranch" ] || die "$DEVELOP: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
 | 
			
		||||
    [ "$1" == init -o -n "$DevelopBranch" ] || die "$DEVELOP: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function init_develop_action() {
 | 
			
		||||
@ -119,7 +161,7 @@ function init_develop_action() {
 | 
			
		||||
$DEVELOP: une branche du même nom existe dans l'origine
 | 
			
		||||
    git checkout $DEVELOP"
 | 
			
		||||
        _ensure_main_branch
 | 
			
		||||
        _ensure_develop_branch
 | 
			
		||||
        _ensure_develop_branch init
 | 
			
		||||
 | 
			
		||||
        resolve_should_push
 | 
			
		||||
 | 
			
		||||
@ -137,7 +179,7 @@ $DEVELOP: une branche du même nom existe dans l'origine
 | 
			
		||||
 | 
			
		||||
function _ensure_upstream_branch() {
 | 
			
		||||
    [ -n "$UPSTREAM" ] || die "La branche UPSTREAM n'a pas été définie"
 | 
			
		||||
    [ -n "$UpstreamBranch" ] || die "$UPSTREAM: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
 | 
			
		||||
    [ "$1" == init -o -n "$UpstreamBranch" ] || die "$UPSTREAM: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function init_upstream_action() {
 | 
			
		||||
@ -148,7 +190,7 @@ function init_upstream_action() {
 | 
			
		||||
$UPSTREAM: une branche du même nom existe dans l'origine
 | 
			
		||||
    git checkout $UPSTREAM"
 | 
			
		||||
        _ensure_develop_branch
 | 
			
		||||
        _ensure_upstream_branch
 | 
			
		||||
        _ensure_upstream_branch init
 | 
			
		||||
 | 
			
		||||
        resolve_should_push
 | 
			
		||||
 | 
			
		||||
@ -182,7 +224,7 @@ $UPSTREAM: une branche du même nom existe dans l'origine
 | 
			
		||||
 | 
			
		||||
function _ensure_dist_branch() {
 | 
			
		||||
    [ -n "$DIST" ] || die "La branche DIST n'a pas été définie"
 | 
			
		||||
    [ -n "$DistBranch" ] || die "$DIST: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
 | 
			
		||||
    [ "$1" == init -o -n "$DistBranch" ] || die "$DIST: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function init_dist_action() {
 | 
			
		||||
@ -193,7 +235,7 @@ function init_dist_action() {
 | 
			
		||||
$DIST: une branche du même nom existe dans l'origine
 | 
			
		||||
    git checkout $DIST"
 | 
			
		||||
        _ensure_main_branch
 | 
			
		||||
        _ensure_dist_branch
 | 
			
		||||
        _ensure_dist_branch init
 | 
			
		||||
 | 
			
		||||
        resolve_should_push
 | 
			
		||||
 | 
			
		||||
@ -242,6 +284,7 @@ function init_action() {
 | 
			
		||||
    case "$what" in
 | 
			
		||||
    init|repo|r) init_repo_action "$@";;
 | 
			
		||||
    config) init_config_action "$@";;
 | 
			
		||||
    composer) init_composer_action "$@";;
 | 
			
		||||
    main|m) checkout_main_action;;
 | 
			
		||||
    develop|dev|d) init_develop_action "$@";;
 | 
			
		||||
    upstream|up|u) init_upstream_action "$@";;
 | 
			
		||||
@ -263,7 +306,8 @@ Origin=
 | 
			
		||||
ForceCreate=
 | 
			
		||||
args=(
 | 
			
		||||
    "gérer un projet git"
 | 
			
		||||
    "repo|config|develop|upstream|dist
 | 
			
		||||
    "repo|config|composer
 | 
			
		||||
develop|upstream|dist
 | 
			
		||||
 | 
			
		||||
INITIALISATION
 | 
			
		||||
 | 
			
		||||
@ -272,6 +316,7 @@ configurer certaines branches du dépôt si elles n'existent pas déjà
 | 
			
		||||
 | 
			
		||||
    repo
 | 
			
		||||
        initialiser un dépôt vide et créer les branches $MAIN et $DEVELOP
 | 
			
		||||
 | 
			
		||||
    develop
 | 
			
		||||
        créer la branche $DEVELOP
 | 
			
		||||
    upstream
 | 
			
		||||
 | 
			
		||||
@ -24,6 +24,8 @@
 | 
			
		||||
		"ext-posix": "*",
 | 
			
		||||
		"ext-pcntl": "*",
 | 
			
		||||
		"ext-curl": "*",
 | 
			
		||||
		"ext-pdo": "*",
 | 
			
		||||
		"ext-pgsql": "*",
 | 
			
		||||
		"ext-sqlite3": "*"
 | 
			
		||||
	},
 | 
			
		||||
	"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",
 | 
			
		||||
        "This file is @generated automatically"
 | 
			
		||||
    ],
 | 
			
		||||
    "content-hash": "266a079e97f3ceecc2cc0a84d6b9743b",
 | 
			
		||||
    "content-hash": "a8b9dc80255663640bda855729ef2d47",
 | 
			
		||||
    "packages": [
 | 
			
		||||
        {
 | 
			
		||||
            "name": "symfony/deprecation-contracts",
 | 
			
		||||
@ -2022,6 +2022,8 @@
 | 
			
		||||
        "ext-posix": "*",
 | 
			
		||||
        "ext-pcntl": "*",
 | 
			
		||||
        "ext-curl": "*",
 | 
			
		||||
        "ext-pdo": "*",
 | 
			
		||||
        "ext-pgsql": "*",
 | 
			
		||||
        "ext-sqlite3": "*"
 | 
			
		||||
    },
 | 
			
		||||
    "plugin-api-version": "2.2.0"
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
namespace nulib;
 | 
			
		||||
 | 
			
		||||
use ArrayAccess;
 | 
			
		||||
use nulib\php\nur_func;
 | 
			
		||||
use nulib\php\func;
 | 
			
		||||
use Traversable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -348,12 +348,12 @@ class cl {
 | 
			
		||||
 | 
			
		||||
  #############################################################################
 | 
			
		||||
 | 
			
		||||
  static final function map(callable $callback, ?iterable $array): array {
 | 
			
		||||
  static final function map($func, ?iterable $array): array {
 | 
			
		||||
    $result = [];
 | 
			
		||||
    if ($array !== null) {
 | 
			
		||||
      $ctx = nur_func::_prepare($callback);
 | 
			
		||||
      $func = func::with($func);
 | 
			
		||||
      foreach ($array as $key => $value) {
 | 
			
		||||
        $result[$key] = nur_func::_call($ctx, [$value, $key]);
 | 
			
		||||
        $result[$key] = $func->invoke([$value, $key]);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $result;
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db;
 | 
			
		||||
 | 
			
		||||
use nulib\php\nur_func;
 | 
			
		||||
use nulib\php\func;
 | 
			
		||||
use nulib\ValueException;
 | 
			
		||||
use Traversable;
 | 
			
		||||
 | 
			
		||||
@ -87,7 +87,7 @@ class Capacitor implements ITransactor {
 | 
			
		||||
    if ($func !== null) {
 | 
			
		||||
      $commited = false;
 | 
			
		||||
      try {
 | 
			
		||||
        nur_func::call($func, $this);
 | 
			
		||||
        func::call($func, $this);
 | 
			
		||||
        if ($commit) {
 | 
			
		||||
          $this->commit();
 | 
			
		||||
          $commited = true;
 | 
			
		||||
@ -120,10 +120,6 @@ class Capacitor implements ITransactor {
 | 
			
		||||
    if ($db->inTransaction()) $db->rollback();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getCreateSql(): string {
 | 
			
		||||
    return $this->storage->_getCreateSql($this->channel);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function exists(): bool {
 | 
			
		||||
    return $this->storage->_exists($this->channel);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@ use Traversable;
 | 
			
		||||
/**
 | 
			
		||||
 * Class CapacitorChannel: un canal d'une instance de {@link ICapacitor}
 | 
			
		||||
 */
 | 
			
		||||
class CapacitorChannel {
 | 
			
		||||
class CapacitorChannel implements ITransactor {
 | 
			
		||||
  const NAME = null;
 | 
			
		||||
 | 
			
		||||
  const TABLE_NAME = null;
 | 
			
		||||
@ -17,6 +17,8 @@ class CapacitorChannel {
 | 
			
		||||
 | 
			
		||||
  const PRIMARY_KEYS = null;
 | 
			
		||||
 | 
			
		||||
  const MIGRATION = null;
 | 
			
		||||
 | 
			
		||||
  const MANAGE_TRANSACTIONS = true;
 | 
			
		||||
 | 
			
		||||
  const EACH_COMMIT_THRESHOLD = 100;
 | 
			
		||||
@ -63,15 +65,29 @@ class CapacitorChannel {
 | 
			
		||||
    $this->created = false;
 | 
			
		||||
    $columnDefinitions = cl::withn(static::COLUMN_DEFINITIONS);
 | 
			
		||||
    $primaryKeys = cl::withn(static::PRIMARY_KEYS);
 | 
			
		||||
    if ($primaryKeys === null && $columnDefinitions !== null) {
 | 
			
		||||
    $migration = cl::withn(static::MIGRATION);
 | 
			
		||||
    if ($columnDefinitions !== null) {
 | 
			
		||||
      # mettre à jour la liste des clés primaires et des migrations
 | 
			
		||||
      $index = 0;
 | 
			
		||||
      foreach ($columnDefinitions as $col => $def) {
 | 
			
		||||
        if ($col === $index) {
 | 
			
		||||
          # si définition séquentielle, seules les définitions de clé
 | 
			
		||||
          # primaires sont supportées
 | 
			
		||||
          $index++;
 | 
			
		||||
          if (preg_match('/\bprimary\s+key\s+\((.+)\)/i', $def, $ms)) {
 | 
			
		||||
            $primaryKeys = preg_split('/\s*,\s*/', trim($ms[1]));
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
        } elseif (is_array($def)) {
 | 
			
		||||
          # tableau: c'est une migration
 | 
			
		||||
          $def = implode(" ", $def);
 | 
			
		||||
          if ($def) {
 | 
			
		||||
            $migration["add_$col"] = "alter table $tableName add column $col $def";
 | 
			
		||||
          } else {
 | 
			
		||||
            $migration["drop_$col"] = "alter table $tableName drop column $col";
 | 
			
		||||
          }
 | 
			
		||||
        } elseif (is_scalar($def)) {
 | 
			
		||||
          # chaine: c'est une définition
 | 
			
		||||
          $def = strval($def);
 | 
			
		||||
          if (preg_match('/\bprimary\s+key\b/i', $def)) {
 | 
			
		||||
            $primaryKeys[] = $col;
 | 
			
		||||
          }
 | 
			
		||||
@ -80,6 +96,7 @@ class CapacitorChannel {
 | 
			
		||||
    }
 | 
			
		||||
    $this->columnDefinitions = $columnDefinitions;
 | 
			
		||||
    $this->primaryKeys = $primaryKeys;
 | 
			
		||||
    $this->migration = $migration;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected string $name;
 | 
			
		||||
@ -192,6 +209,12 @@ class CapacitorChannel {
 | 
			
		||||
    return $this->columnDefinitions;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected ?array $migration;
 | 
			
		||||
 | 
			
		||||
  function getMigration(): ?array {
 | 
			
		||||
    return $this->migration;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected ?array $primaryKeys;
 | 
			
		||||
 | 
			
		||||
  function getPrimaryKeys(): ?array {
 | 
			
		||||
@ -245,9 +268,6 @@ class CapacitorChannel {
 | 
			
		||||
    return $serial !== null? unserialize($serial): null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const SERIAL_DEFINITION = "mediumtext";
 | 
			
		||||
  const SUM_DEFINITION = "varchar(40)";
 | 
			
		||||
 | 
			
		||||
  final function sum(?string $serial, $value=null): ?string {
 | 
			
		||||
    if ($serial === null) $serial = $this->serialize($value);
 | 
			
		||||
    return $serial !== null? sha1($serial): null;
 | 
			
		||||
@ -377,6 +397,42 @@ class CapacitorChannel {
 | 
			
		||||
    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 {
 | 
			
		||||
    return $this->capacitor->charge($item, $func, $args, $values);
 | 
			
		||||
  }
 | 
			
		||||
@ -404,4 +460,8 @@ class CapacitorChannel {
 | 
			
		||||
  function delete($filter, $func=null, ?array $args=null): int {
 | 
			
		||||
    return $this->capacitor->delete($filter, $func, $args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function close(): void {
 | 
			
		||||
    $this->capacitor->close();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,8 +2,9 @@
 | 
			
		||||
namespace nulib\db;
 | 
			
		||||
 | 
			
		||||
use nulib\cl;
 | 
			
		||||
use nulib\db\_private\_migration;
 | 
			
		||||
use nulib\db\cache\cache;
 | 
			
		||||
use nulib\php\nur_func;
 | 
			
		||||
use nulib\php\func;
 | 
			
		||||
use nulib\ValueException;
 | 
			
		||||
use Traversable;
 | 
			
		||||
 | 
			
		||||
@ -35,14 +36,18 @@ abstract class CapacitorStorage {
 | 
			
		||||
  /** DOIT être défini dans les classes dérivées */
 | 
			
		||||
  const PRIMARY_KEY_DEFINITION = null;
 | 
			
		||||
 | 
			
		||||
  const SERDATA_DEFINITION = "mediumtext";
 | 
			
		||||
  const SERSUM_DEFINITION = "varchar(40)";
 | 
			
		||||
  const SERTS_DEFINITION = "datetime";
 | 
			
		||||
 | 
			
		||||
  const COLUMN_DEFINITIONS = [
 | 
			
		||||
    "item__" => CapacitorChannel::SERIAL_DEFINITION,
 | 
			
		||||
    "item__sum_" => CapacitorChannel::SUM_DEFINITION,
 | 
			
		||||
    "created_" => "datetime",
 | 
			
		||||
    "modified_" => "datetime",
 | 
			
		||||
    "item__" => "serdata",
 | 
			
		||||
    "item__sum_" => "sersum",
 | 
			
		||||
    "created_" => "serts",
 | 
			
		||||
    "modified_" => "serts",
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  protected function ColumnDefinitions(CapacitorChannel $channel): array {
 | 
			
		||||
  protected function ColumnDefinitions(CapacitorChannel $channel, bool $ignoreMigrations=false): array {
 | 
			
		||||
    $definitions = [];
 | 
			
		||||
    if ($channel->getPrimaryKeys() === null) {
 | 
			
		||||
      $definitions[] = static::PRIMARY_KEY_DEFINITION;
 | 
			
		||||
@ -56,16 +61,31 @@ abstract class CapacitorStorage {
 | 
			
		||||
    $constraints = [];
 | 
			
		||||
    $index = 0;
 | 
			
		||||
    foreach ($tmp as $col => $def) {
 | 
			
		||||
      switch ($def) {
 | 
			
		||||
      case "serdata": $def = static::SERDATA_DEFINITION; break;
 | 
			
		||||
      case "sersum": $def = static::SERSUM_DEFINITION; break;
 | 
			
		||||
      case "serts": $def = static::SERTS_DEFINITION; break;
 | 
			
		||||
      }
 | 
			
		||||
      if ($col === $index) {
 | 
			
		||||
        $index++;
 | 
			
		||||
        $constraints[] = $def;
 | 
			
		||||
      } else {
 | 
			
		||||
        $definitions[$col] = $def;
 | 
			
		||||
      } elseif (is_array($def)) {
 | 
			
		||||
        # éventuellement, ignorer les migrations
 | 
			
		||||
        $def = implode(" ", $def);
 | 
			
		||||
        if ($def && !$ignoreMigrations) {
 | 
			
		||||
          $definitions[$col] = $def;
 | 
			
		||||
        }
 | 
			
		||||
      } elseif (is_scalar($def)) {
 | 
			
		||||
        $definitions[$col] = strval($def);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return cl::merge($definitions, $constraints);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function getMigration(CapacitorChannel $channel): ?array {
 | 
			
		||||
    return $channel->getMigration();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** sérialiser les valeurs qui doivent l'être dans $values */
 | 
			
		||||
  protected function serialize(CapacitorChannel $channel, ?array $values): ?array {
 | 
			
		||||
    if ($values === null) return null;
 | 
			
		||||
@ -128,11 +148,10 @@ abstract class CapacitorStorage {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function _createSql(CapacitorChannel $channel): array {
 | 
			
		||||
    $cols = $this->ColumnDefinitions($channel);
 | 
			
		||||
    return [
 | 
			
		||||
      "create table if not exists",
 | 
			
		||||
      "table" => $channel->getTableName(),
 | 
			
		||||
      "cols" => $cols,
 | 
			
		||||
      "cols" => $this->ColumnDefinitions($channel, true),
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -147,20 +166,45 @@ $sql;
 | 
			
		||||
EOT;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  abstract function _getCreateSql(CapacitorChannel $channel): string;
 | 
			
		||||
  abstract function _getMigration(CapacitorChannel $channel): _migration;
 | 
			
		||||
 | 
			
		||||
  /** obtenir la requête SQL utilisée pour créer la table */
 | 
			
		||||
  function getCreateSql(?string $channel): string {
 | 
			
		||||
    return $this->_getCreateSql($this->getChannel($channel));
 | 
			
		||||
  const CHANNELS_TABLE = "_channels";
 | 
			
		||||
  const CHANNELS_COLS = [
 | 
			
		||||
    "name" => "varchar primary key",
 | 
			
		||||
    "table_name" => "varchar",
 | 
			
		||||
    "class_name" => "varchar",
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  protected function _createChannelsSql(): array {
 | 
			
		||||
    return [
 | 
			
		||||
      "create table if not exists",
 | 
			
		||||
      "table" => static::CHANNELS_TABLE,
 | 
			
		||||
      "cols" => static::CHANNELS_COLS,
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function _addToChannelsSql(CapacitorChannel $channel): array {
 | 
			
		||||
    return [
 | 
			
		||||
      "insert",
 | 
			
		||||
      "into" => static::CHANNELS_TABLE,
 | 
			
		||||
      "values" => [
 | 
			
		||||
        "name" => $channel->getName(),
 | 
			
		||||
        "table_name" => $channel->getTableName(),
 | 
			
		||||
        "class_name" => get_class($channel),
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function _afterCreate(CapacitorChannel $channel): void {
 | 
			
		||||
    $db = $this->db();
 | 
			
		||||
    $db->exec($this->_createChannelsSql());
 | 
			
		||||
    $db->exec($this->_addToChannelsSql($channel));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function _create(CapacitorChannel $channel): void {
 | 
			
		||||
    $channel->ensureSetup();
 | 
			
		||||
    if (!$channel->isCreated()) {
 | 
			
		||||
      $this->db->exec($this->_createSql($channel));
 | 
			
		||||
      $this->_getMigration($channel)->migrate($this->db());
 | 
			
		||||
      $this->_afterCreate($channel);
 | 
			
		||||
      $channel->setCreated();
 | 
			
		||||
    }
 | 
			
		||||
@ -183,12 +227,28 @@ EOT;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function _beforeReset(CapacitorChannel $channel): void {
 | 
			
		||||
    $db = $this->db;
 | 
			
		||||
    $name = $channel->getName();
 | 
			
		||||
    $db->exec([
 | 
			
		||||
      "delete",
 | 
			
		||||
      "from" => _migration::MIGRATION_TABLE,
 | 
			
		||||
      "where" => [
 | 
			
		||||
        "channel" => $name,
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
    $db->exec([
 | 
			
		||||
      "delete",
 | 
			
		||||
      "from" => static::CHANNELS_TABLE,
 | 
			
		||||
      "where" => [
 | 
			
		||||
        "name" => $name,
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** supprimer le canal spécifié */
 | 
			
		||||
  function _reset(CapacitorChannel $channel, bool $recreate=false): void {
 | 
			
		||||
    $this->_beforeReset($channel);
 | 
			
		||||
    $this->db->exec([
 | 
			
		||||
    $this->db()->exec([
 | 
			
		||||
      "drop table if exists",
 | 
			
		||||
      $channel->getTableName(),
 | 
			
		||||
    ]);
 | 
			
		||||
@ -230,10 +290,7 @@ EOT;
 | 
			
		||||
    $db = $this->db();
 | 
			
		||||
    $args ??= [];
 | 
			
		||||
 | 
			
		||||
    $initFunc = [$channel, "getItemValues"];
 | 
			
		||||
    $initArgs = $args;
 | 
			
		||||
    nur_func::ensure_func($initFunc, null, $initArgs);
 | 
			
		||||
    $values = nur_func::call($initFunc, $item, ...$initArgs);
 | 
			
		||||
    $values = func::call([$channel, "getItemValues"], $item, ...$args);
 | 
			
		||||
    if ($values === [false]) return 0;
 | 
			
		||||
 | 
			
		||||
    $row = cl::merge(
 | 
			
		||||
@ -259,9 +316,7 @@ EOT;
 | 
			
		||||
        "modified_" => $now,
 | 
			
		||||
      ]);
 | 
			
		||||
      $insert = true;
 | 
			
		||||
      $initFunc = [$channel, "onCreate"];
 | 
			
		||||
      $initArgs = $args;
 | 
			
		||||
      nur_func::ensure_func($initFunc, null, $initArgs);
 | 
			
		||||
      $initFunc = func::with([$channel, "onCreate"], $args);
 | 
			
		||||
      $values = $this->unserialize($channel, $row);
 | 
			
		||||
      $pvalues = null;
 | 
			
		||||
    } else {
 | 
			
		||||
@ -276,14 +331,12 @@ EOT;
 | 
			
		||||
      } else {
 | 
			
		||||
        $row = cl::merge($prow, $row);
 | 
			
		||||
      }
 | 
			
		||||
      $initFunc = [$channel, "onUpdate"];
 | 
			
		||||
      $initArgs = $args;
 | 
			
		||||
      nur_func::ensure_func($initFunc, null, $initArgs);
 | 
			
		||||
      $initFunc = func::with([$channel, "onUpdate"], $args);
 | 
			
		||||
      $values = $this->unserialize($channel, $row);
 | 
			
		||||
      $pvalues = $this->unserialize($channel, $prow);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $updates = nur_func::call($initFunc, $item, $values, $pvalues, ...$initArgs);
 | 
			
		||||
    $updates = $initFunc->prependArgs([$item, $values, $pvalues])->invoke();
 | 
			
		||||
    if ($updates === [false]) return 0;
 | 
			
		||||
    if (is_array($updates) && $updates) {
 | 
			
		||||
      if ($insert === null) $insert = false;
 | 
			
		||||
@ -295,8 +348,10 @@ EOT;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($func !== null) {
 | 
			
		||||
      nur_func::ensure_func($func, $channel, $args);
 | 
			
		||||
      $updates = nur_func::call($func, $item, $values, $pvalues, ...$args);
 | 
			
		||||
      $updates = func::with($func)
 | 
			
		||||
        ->prependArgs([$item, $values, $pvalues])
 | 
			
		||||
        ->bind($channel, true)
 | 
			
		||||
        ->invoke();
 | 
			
		||||
      if ($updates === [false]) return 0;
 | 
			
		||||
      if (is_array($updates) && $updates) {
 | 
			
		||||
        if ($insert === null) $insert = false;
 | 
			
		||||
@ -510,8 +565,7 @@ EOT;
 | 
			
		||||
  function _each(CapacitorChannel $channel, $filter, $func, ?array $args, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
 | 
			
		||||
    $this->_create($channel);
 | 
			
		||||
    if ($func === null) $func = CapacitorChannel::onEach;
 | 
			
		||||
    nur_func::ensure_func($func, $channel, $args);
 | 
			
		||||
    $onEach = nur_func::_prepare($func);
 | 
			
		||||
    $onEach = func::with($func)->bind($channel, true);
 | 
			
		||||
    $db = $this->db();
 | 
			
		||||
    # si on est déjà dans une transaction, désactiver la gestion des transactions
 | 
			
		||||
    $manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
 | 
			
		||||
@ -528,7 +582,7 @@ EOT;
 | 
			
		||||
      $all = $this->_allCached("each", $channel, $filter, $mergeQuery);
 | 
			
		||||
      foreach ($all as $values) {
 | 
			
		||||
        $rowIds = $this->getRowIds($channel, $values);
 | 
			
		||||
        $updates = nur_func::_call($onEach, [$values["item"], $values, ...$args]);
 | 
			
		||||
        $updates = $onEach->invoke([$values["item"], $values, ...$args]);
 | 
			
		||||
        if (is_array($updates) && $updates) {
 | 
			
		||||
          if (!array_key_exists("modified_", $updates)) {
 | 
			
		||||
            $updates["modified_"] = date("Y-m-d H:i:s");
 | 
			
		||||
@ -579,8 +633,7 @@ EOT;
 | 
			
		||||
  function _delete(CapacitorChannel $channel, $filter, $func, ?array $args): int {
 | 
			
		||||
    $this->_create($channel);
 | 
			
		||||
    if ($func === null) $func = CapacitorChannel::onDelete;
 | 
			
		||||
    nur_func::ensure_func($func, $channel, $args);
 | 
			
		||||
    $onEach = nur_func::_prepare($func);
 | 
			
		||||
    $onEach = func::with($func)->bind($channel, true);
 | 
			
		||||
    $db = $this->db();
 | 
			
		||||
    # si on est déjà dans une transaction, désactiver la gestion des transactions
 | 
			
		||||
    $manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
 | 
			
		||||
@ -596,7 +649,7 @@ EOT;
 | 
			
		||||
      $all = $this->_allCached("delete", $channel, $filter);
 | 
			
		||||
      foreach ($all as $values) {
 | 
			
		||||
        $rowIds = $this->getRowIds($channel, $values);
 | 
			
		||||
        $delete = boolval(nur_func::_call($onEach, [$values["item"], $values, ...$args]));
 | 
			
		||||
        $delete = boolval($onEach->invoke([$values["item"], $values, ...$args]));
 | 
			
		||||
        if ($delete) {
 | 
			
		||||
          $db->exec([
 | 
			
		||||
            "delete",
 | 
			
		||||
 | 
			
		||||
@ -15,5 +15,9 @@ interface IDatabase extends ITransactor {
 | 
			
		||||
 | 
			
		||||
  function one($query, ?array $params=null): ?array;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * si $primaryKeys est fourni, le résultat est indexé sur la(es) colonne(s)
 | 
			
		||||
   * spécifiée(s)
 | 
			
		||||
   */
 | 
			
		||||
  function all($query, ?array $params=null, $primaryKeys=null): iterable;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -11,12 +11,13 @@ interface ITransactor {
 | 
			
		||||
   */
 | 
			
		||||
  function willUpdate(...$transactors): self;
 | 
			
		||||
 | 
			
		||||
  /** Indiquer si une transaction est en cours */
 | 
			
		||||
  function inTransaction(): bool;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * démarrer une transaction
 | 
			
		||||
   *
 | 
			
		||||
   * si $func!==null, l'apppeler. ensuite, si $commit===true, commiter la
 | 
			
		||||
   * si $func!==null, l'apppeler. ensuite, si $commit===true, valider la
 | 
			
		||||
   * transaction. si une erreur se produit lors de l'appel de la fonction,
 | 
			
		||||
   * annuler la transaction
 | 
			
		||||
   *
 | 
			
		||||
@ -24,7 +25,9 @@ interface ITransactor {
 | 
			
		||||
   */
 | 
			
		||||
  function beginTransaction(?callable $func=null, bool $commit=true): void;
 | 
			
		||||
 | 
			
		||||
  /** valider la transaction */
 | 
			
		||||
  function commit(): void;
 | 
			
		||||
 | 
			
		||||
  /** annuler la transaction */
 | 
			
		||||
  function rollback(): void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,39 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\_private;
 | 
			
		||||
 | 
			
		||||
trait Tcreate {
 | 
			
		||||
  static function isa(string $sql): bool {
 | 
			
		||||
    //return preg_match("/^create(?:\s+table)?\b/i", $sql);
 | 
			
		||||
    #XXX implémentation minimale
 | 
			
		||||
    return preg_match("/^create\s+table\b/i", $sql);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static function parse(array $query, ?array &$bindings=null): string {
 | 
			
		||||
    #XXX implémentation minimale
 | 
			
		||||
    $sql = [self::merge_seq($query)];
 | 
			
		||||
 | 
			
		||||
    ## préfixe
 | 
			
		||||
    if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
 | 
			
		||||
 | 
			
		||||
    ## table
 | 
			
		||||
    $sql[] = $query["table"];
 | 
			
		||||
 | 
			
		||||
    ## columns
 | 
			
		||||
    $cols = $query["cols"];
 | 
			
		||||
    $index = 0;
 | 
			
		||||
    foreach ($cols as $col => &$definition) {
 | 
			
		||||
      if ($col === $index) {
 | 
			
		||||
        $index++;
 | 
			
		||||
      } else {
 | 
			
		||||
        $definition = "$col $definition";
 | 
			
		||||
      }
 | 
			
		||||
    }; unset($definition);
 | 
			
		||||
    $sql[] = "(\n  ".implode("\n, ", $cols)."\n)";
 | 
			
		||||
 | 
			
		||||
    ## suffixe
 | 
			
		||||
    if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
 | 
			
		||||
 | 
			
		||||
    ## fin de la requête
 | 
			
		||||
    return implode(" ", $sql);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,38 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\_private;
 | 
			
		||||
 | 
			
		||||
trait Tdelete {
 | 
			
		||||
 | 
			
		||||
  static function isa(string $sql): bool {
 | 
			
		||||
    return preg_match("/^delete(?:\s+from)?\b/i", $sql);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static function parse(array $query, ?array &$bindings=null): string {
 | 
			
		||||
    #XXX implémentation minimale
 | 
			
		||||
    $tmpsql = self::merge_seq($query);
 | 
			
		||||
    self::consume('delete(?:\s+from)?\b', $tmpsql);
 | 
			
		||||
    $sql = ["delete from", $tmpsql];
 | 
			
		||||
 | 
			
		||||
    ## préfixe
 | 
			
		||||
    if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
 | 
			
		||||
 | 
			
		||||
    ## table
 | 
			
		||||
    $sql[] = $query["from"];
 | 
			
		||||
 | 
			
		||||
    ## where
 | 
			
		||||
    $where = $query["where"] ?? null;
 | 
			
		||||
    if ($where !== null) {
 | 
			
		||||
      self::parse_conds($where, $wheresql, $bindings);
 | 
			
		||||
      if ($wheresql) {
 | 
			
		||||
        $sql[] = "where";
 | 
			
		||||
        $sql[] = implode(" and ", $wheresql);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ## suffixe
 | 
			
		||||
    if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
 | 
			
		||||
 | 
			
		||||
    ## fin de la requête
 | 
			
		||||
    return implode(" ", $sql);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,18 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\_private;
 | 
			
		||||
 | 
			
		||||
use nulib\cl;
 | 
			
		||||
use nulib\ValueException;
 | 
			
		||||
 | 
			
		||||
trait Tgeneric {
 | 
			
		||||
  static function isa(string $sql): bool {
 | 
			
		||||
    return preg_match('/^(?:drop\s+table)\b/i', $sql);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static function parse(array $query, ?array &$bindings=null): string {
 | 
			
		||||
    if (!cl::is_list($query)) {
 | 
			
		||||
      throw new ValueException("Seuls les tableaux séquentiels sont supportés");
 | 
			
		||||
    }
 | 
			
		||||
    return self::merge_seq($query);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,82 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\_private;
 | 
			
		||||
 | 
			
		||||
use nulib\cl;
 | 
			
		||||
use nulib\ValueException;
 | 
			
		||||
 | 
			
		||||
trait Tinsert {
 | 
			
		||||
  static function isa(string $sql): bool {
 | 
			
		||||
    return preg_match("/^insert\b/i", $sql);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * parser une chaine de la forme
 | 
			
		||||
   * "insert [into] [TABLE] [(COLS)] [values (VALUES)]"
 | 
			
		||||
   */
 | 
			
		||||
  static function parse(array $query, ?array &$bindings=null): string {
 | 
			
		||||
    # fusionner d'abord toutes les parties séquentielles
 | 
			
		||||
    $usersql = $tmpsql = self::merge_seq($query);
 | 
			
		||||
 | 
			
		||||
    ### vérifier la présence des parties nécessaires
 | 
			
		||||
    $sql = [];
 | 
			
		||||
    if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
 | 
			
		||||
 | 
			
		||||
    ## insert
 | 
			
		||||
    self::consume('(insert(?:\s+or\s+(?:ignore|replace))?)\s*', $tmpsql, $ms);
 | 
			
		||||
    $sql[] = $ms[1];
 | 
			
		||||
 | 
			
		||||
    ## into
 | 
			
		||||
    self::consume('into\s*', $tmpsql);
 | 
			
		||||
    $sql[] = "into";
 | 
			
		||||
    $into = $query["into"] ?? null;
 | 
			
		||||
    if (self::consume('([a-z_][a-z0-9_]*)\s*', $tmpsql, $ms)) {
 | 
			
		||||
      if ($into === null) $into = $ms[1];
 | 
			
		||||
      $sql[] = $into;
 | 
			
		||||
    } elseif ($into !== null) {
 | 
			
		||||
      $sql[] = $into;
 | 
			
		||||
    } else {
 | 
			
		||||
      throw new ValueException("expected table name: $usersql");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ## cols & values
 | 
			
		||||
    $usercols = [];
 | 
			
		||||
    $uservalues = [];
 | 
			
		||||
    if (self::consume('\(([^)]*)\)\s*', $tmpsql, $ms)) {
 | 
			
		||||
      $usercols = array_merge($usercols, preg_split("/\s*,\s*/", $ms[1]));
 | 
			
		||||
    }
 | 
			
		||||
    $cols = cl::withn($query["cols"] ?? null);
 | 
			
		||||
    $values = cl::withn($query["values"] ?? null);
 | 
			
		||||
    $schema = $query["schema"] ?? null;
 | 
			
		||||
    if ($cols === null) {
 | 
			
		||||
      if ($usercols) {
 | 
			
		||||
        $cols = $usercols;
 | 
			
		||||
      } elseif ($values) {
 | 
			
		||||
        $cols = array_keys($values);
 | 
			
		||||
        $usercols = array_merge($usercols, $cols);
 | 
			
		||||
      } elseif ($schema && is_array($schema)) {
 | 
			
		||||
        #XXX implémenter support AssocSchema
 | 
			
		||||
        $cols = array_keys($schema);
 | 
			
		||||
        $usercols = array_merge($usercols, $cols);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (self::consume('values\s+\(\s*(.*)\s*\)\s*', $tmpsql, $ms)) {
 | 
			
		||||
      if ($ms[1]) $uservalues[] = $ms[1];
 | 
			
		||||
    }
 | 
			
		||||
    if ($cols !== null && !$uservalues) {
 | 
			
		||||
      if (!$usercols) $usercols = $cols;
 | 
			
		||||
      foreach ($cols as $col) {
 | 
			
		||||
        $uservalues[] = ":$col";
 | 
			
		||||
        $bindings[$col] = $values[$col] ?? null;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $sql[] = "(" . implode(", ", $usercols) . ")";
 | 
			
		||||
    $sql[] = "values (" . implode(", ", $uservalues) . ")";
 | 
			
		||||
 | 
			
		||||
    ## suffixe
 | 
			
		||||
    if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
 | 
			
		||||
 | 
			
		||||
    ## fin de la requête
 | 
			
		||||
    self::check_eof($tmpsql, $usersql);
 | 
			
		||||
    return implode(" ", $sql);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,168 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\_private;
 | 
			
		||||
 | 
			
		||||
use nulib\cl;
 | 
			
		||||
use nulib\str;
 | 
			
		||||
use nulib\ValueException;
 | 
			
		||||
 | 
			
		||||
trait Tselect {
 | 
			
		||||
  static function isa(string $sql): bool {
 | 
			
		||||
    return preg_match("/^select\b/i", $sql);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static function add_prefix(string $col, ?string $prefix): string {
 | 
			
		||||
    if ($prefix === null) return $col;
 | 
			
		||||
    if (strpos($col, ".") !== false) return $col;
 | 
			
		||||
    return "$prefix$col";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * parser une chaine de la forme
 | 
			
		||||
   * "select [COLS] [from TABLE] [where CONDS] [order by ORDERS] [group by GROUPS] [having CONDS]"
 | 
			
		||||
   */
 | 
			
		||||
  static function parse(array $query, ?array &$bindings=null): string {
 | 
			
		||||
    # fusionner d'abord toutes les parties séquentielles
 | 
			
		||||
    $usersql = $tmpsql = self::merge_seq($query);
 | 
			
		||||
 | 
			
		||||
    ### vérifier la présence des parties nécessaires
 | 
			
		||||
    $sql = [];
 | 
			
		||||
 | 
			
		||||
    ## préfixe
 | 
			
		||||
    if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
 | 
			
		||||
 | 
			
		||||
    ## select
 | 
			
		||||
    self::consume('(select(?:\s*distinct)?)\s*', $tmpsql, $ms);
 | 
			
		||||
    $sql[] = $ms[1];
 | 
			
		||||
 | 
			
		||||
    ## cols
 | 
			
		||||
    $usercols = [];
 | 
			
		||||
    if (self::consume('(.*?)\s*(?=$|\bfrom\b)', $tmpsql, $ms)) {
 | 
			
		||||
      if ($ms[1]) $usercols[] = $ms[1];
 | 
			
		||||
    }
 | 
			
		||||
    $colPrefix = $query["col_prefix"] ?? null;
 | 
			
		||||
    if ($colPrefix !== null) str::add_suffix($colPrefix, ".");
 | 
			
		||||
    $tmpcols = cl::withn($query["cols"] ?? null);
 | 
			
		||||
    $schema = $query["schema"] ?? null;
 | 
			
		||||
    if ($tmpcols !== null) {
 | 
			
		||||
      $cols = [];
 | 
			
		||||
      $index = 0;
 | 
			
		||||
      foreach ($tmpcols as $key => $col) {
 | 
			
		||||
        if ($key === $index) {
 | 
			
		||||
          $index++;
 | 
			
		||||
          $cols[] = $col;
 | 
			
		||||
          $usercols[] = self::add_prefix($col, $colPrefix);
 | 
			
		||||
        } else {
 | 
			
		||||
          $cols[] = $key;
 | 
			
		||||
          $usercols[] = self::add_prefix($col, $colPrefix)." as $key";
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      $cols = null;
 | 
			
		||||
      if ($schema && is_array($schema) && !in_array("*", $usercols)) {
 | 
			
		||||
        $cols = array_keys($schema);
 | 
			
		||||
        foreach ($cols as $col) {
 | 
			
		||||
          $usercols[] = self::add_prefix($col, $colPrefix);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (!$usercols && !$cols) $usercols = [self::add_prefix("*", $colPrefix)];
 | 
			
		||||
    $sql[] = implode(", ", $usercols);
 | 
			
		||||
 | 
			
		||||
    ## from
 | 
			
		||||
    $from = $query["from"] ?? null;
 | 
			
		||||
    if (self::consume('from\s+([a-z_][a-z0-9_]*)\s*(?=;?\s*$|\bwhere\b)', $tmpsql, $ms)) {
 | 
			
		||||
      if ($from === null) $from = $ms[1];
 | 
			
		||||
      $sql[] = "from";
 | 
			
		||||
      $sql[] = $from;
 | 
			
		||||
    } elseif ($from !== null) {
 | 
			
		||||
      $sql[] = "from";
 | 
			
		||||
      $sql[] = $from;
 | 
			
		||||
    } else {
 | 
			
		||||
      throw new ValueException("expected table name: $usersql");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ## where
 | 
			
		||||
    $userwhere = [];
 | 
			
		||||
    if (self::consume('where\b\s*(.*?)(?=;?\s*$|\border\s+by\b)', $tmpsql, $ms)) {
 | 
			
		||||
      if ($ms[1]) $userwhere[] = $ms[1];
 | 
			
		||||
    }
 | 
			
		||||
    $where = cl::withn($query["where"] ?? null);
 | 
			
		||||
    if ($where !== null) self::parse_conds($where, $userwhere, $bindings);
 | 
			
		||||
    if ($userwhere) {
 | 
			
		||||
      $sql[] = "where";
 | 
			
		||||
      $sql[] = implode(" and ", $userwhere);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ## order by
 | 
			
		||||
    $userorderby = [];
 | 
			
		||||
    if (self::consume('order\s+by\b\s*(.*?)(?=;?\s*$|\bgroup\s+by\b)', $tmpsql, $ms)) {
 | 
			
		||||
      if ($ms[1]) $userorderby[] = $ms[1];
 | 
			
		||||
    }
 | 
			
		||||
    $orderby = cl::withn($query["order by"] ?? null);
 | 
			
		||||
    if ($orderby !== null) {
 | 
			
		||||
      $index = 0;
 | 
			
		||||
      foreach ($orderby as $key => $value) {
 | 
			
		||||
        if ($key === $index) {
 | 
			
		||||
          $userorderby[] = $value;
 | 
			
		||||
          $index++;
 | 
			
		||||
        } else {
 | 
			
		||||
          if ($value === null) $value = false;
 | 
			
		||||
          if (!is_bool($value)) {
 | 
			
		||||
            $userorderby[] = "$key $value";
 | 
			
		||||
          } elseif ($value) {
 | 
			
		||||
            $userorderby[] = $key;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if ($userorderby) {
 | 
			
		||||
      $sql[] = "order by";
 | 
			
		||||
      $sql[] = implode(", ", $userorderby);
 | 
			
		||||
    }
 | 
			
		||||
    ## group by
 | 
			
		||||
    $usergroupby = [];
 | 
			
		||||
    if (self::consume('group\s+by\b\s*(.*?)(?=;?\s*$|\bhaving\b)', $tmpsql, $ms)) {
 | 
			
		||||
      if ($ms[1]) $usergroupby[] = $ms[1];
 | 
			
		||||
    }
 | 
			
		||||
    $groupby = cl::withn($query["group by"] ?? null);
 | 
			
		||||
    if ($groupby !== null) {
 | 
			
		||||
      $index = 0;
 | 
			
		||||
      foreach ($groupby as $key => $value) {
 | 
			
		||||
        if ($key === $index) {
 | 
			
		||||
          $usergroupby[] = $value;
 | 
			
		||||
          $index++;
 | 
			
		||||
        } else {
 | 
			
		||||
          if ($value === null) $value = false;
 | 
			
		||||
          if (!is_bool($value)) {
 | 
			
		||||
            $usergroupby[] = "$key $value";
 | 
			
		||||
          } elseif ($value) {
 | 
			
		||||
            $usergroupby[] = $key;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if ($usergroupby) {
 | 
			
		||||
      $sql[] = "group by";
 | 
			
		||||
      $sql[] = implode(", ", $usergroupby);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ## having
 | 
			
		||||
    $userhaving = [];
 | 
			
		||||
    if (self::consume('having\b\s*(.*?)(?=;?\s*$)', $tmpsql, $ms)) {
 | 
			
		||||
      if ($ms[1]) $userhaving[] = $ms[1];
 | 
			
		||||
    }
 | 
			
		||||
    $having = cl::withn($query["having"] ?? null);
 | 
			
		||||
    if ($having !== null) self::parse_conds($having, $userhaving, $bindings);
 | 
			
		||||
    if ($userhaving) {
 | 
			
		||||
      $sql[] = "having";
 | 
			
		||||
      $sql[] = implode(" and ", $userhaving);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ## suffixe
 | 
			
		||||
    if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
 | 
			
		||||
 | 
			
		||||
    ## fin de la requête
 | 
			
		||||
    self::check_eof($tmpsql, $usersql);
 | 
			
		||||
    return implode(" ", $sql);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,40 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\_private;
 | 
			
		||||
 | 
			
		||||
trait Tupdate {
 | 
			
		||||
  static function isa(string $sql): bool {
 | 
			
		||||
    return preg_match("/^update\b/i", $sql);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static function parse(array $query, ?array &$bindings=null): string {
 | 
			
		||||
    #XXX implémentation minimale
 | 
			
		||||
    $sql = [self::merge_seq($query)];
 | 
			
		||||
 | 
			
		||||
    ## préfixe
 | 
			
		||||
    if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
 | 
			
		||||
 | 
			
		||||
    ## table
 | 
			
		||||
    $sql[] = $query["table"];
 | 
			
		||||
 | 
			
		||||
    ## set
 | 
			
		||||
    self::parse_set_values($query["values"], $setsql, $bindings);
 | 
			
		||||
    $sql[] = "set";
 | 
			
		||||
    $sql[] = implode(", ", $setsql);
 | 
			
		||||
 | 
			
		||||
    ## where
 | 
			
		||||
    $where = $query["where"] ?? null;
 | 
			
		||||
    if ($where !== null) {
 | 
			
		||||
      self::parse_conds($where, $wheresql, $bindings);
 | 
			
		||||
      if ($wheresql) {
 | 
			
		||||
        $sql[] = "where";
 | 
			
		||||
        $sql[] = implode(" and ", $wheresql);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ## suffixe
 | 
			
		||||
    if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
 | 
			
		||||
 | 
			
		||||
    ## fin de la requête
 | 
			
		||||
    return implode(" ", $sql);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -5,256 +5,58 @@ use nulib\cl;
 | 
			
		||||
use nulib\str;
 | 
			
		||||
use nulib\ValueException;
 | 
			
		||||
 | 
			
		||||
abstract class _base {
 | 
			
		||||
  protected static function consume(string $pattern, string &$string, ?array &$ms=null): bool {
 | 
			
		||||
    if (!preg_match("/^$pattern/i", $string, $ms)) return false;
 | 
			
		||||
    $string = substr($string, strlen($ms[0]));
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** fusionner toutes les parties séquentielles d'une requête */
 | 
			
		||||
  protected static function merge_seq(array $query): string {
 | 
			
		||||
    $index = 0;
 | 
			
		||||
    $sql = "";
 | 
			
		||||
    foreach ($query as $key => $value) {
 | 
			
		||||
      if ($key === $index) {
 | 
			
		||||
        $index++;
 | 
			
		||||
        if ($sql && !str::ends_with(" ", $sql) && !str::starts_with(" ", $value)) {
 | 
			
		||||
          $sql .= " ";
 | 
			
		||||
        }
 | 
			
		||||
        $sql .= $value;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $sql;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected static function is_sep(&$cond): bool {
 | 
			
		||||
    if (!is_string($cond)) return false;
 | 
			
		||||
    if (!preg_match('/^\s*(and|or|not)\s*$/i', $cond, $ms)) return false;
 | 
			
		||||
    $cond = $ms[1];
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static function parse_conds(?array $conds, ?array &$sql, ?array &$bindings): void {
 | 
			
		||||
    if (!$conds) return;
 | 
			
		||||
    $sep = null;
 | 
			
		||||
    $index = 0;
 | 
			
		||||
    $condsql = [];
 | 
			
		||||
    foreach ($conds as $key => $cond) {
 | 
			
		||||
      if ($key === $index) {
 | 
			
		||||
        ## séquentiel
 | 
			
		||||
        if ($index === 0 && self::is_sep($cond)) {
 | 
			
		||||
          $sep = $cond;
 | 
			
		||||
        } elseif (is_bool($cond)) {
 | 
			
		||||
          # ignorer les valeurs true et false
 | 
			
		||||
        } elseif (is_array($cond)) {
 | 
			
		||||
          # condition récursive
 | 
			
		||||
          self::parse_conds($cond, $condsql, $bindings);
 | 
			
		||||
        } 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);
 | 
			
		||||
abstract class _base extends _common {
 | 
			
		||||
  protected static function verifix(&$sql, ?array &$bindings=null, ?array &$meta=null): void {
 | 
			
		||||
    if (is_array($sql)) {
 | 
			
		||||
      $prefix = $sql[0] ?? null;
 | 
			
		||||
      if ($prefix === null) {
 | 
			
		||||
        throw new ValueException("requête invalide");
 | 
			
		||||
      } elseif (_create::isa($prefix)) {
 | 
			
		||||
        $sql = _create::parse($sql, $bindings);
 | 
			
		||||
        $meta = ["isa" => "create", "type" => "ddl"];
 | 
			
		||||
      } elseif (_select::isa($prefix)) {
 | 
			
		||||
        $sql = _select::parse($sql, $bindings);
 | 
			
		||||
        $meta = ["isa" => "select", "type" => "dql"];
 | 
			
		||||
      } elseif (_insert::isa($prefix)) {
 | 
			
		||||
        $sql = _insert::parse($sql, $bindings);
 | 
			
		||||
        $meta = ["isa" => "insert", "type" => "dml"];
 | 
			
		||||
      } elseif (_update::isa($prefix)) {
 | 
			
		||||
        $sql = _update::parse($sql, $bindings);
 | 
			
		||||
        $meta = ["isa" => "update", "type" => "dml"];
 | 
			
		||||
      } elseif (_delete::isa($prefix)) {
 | 
			
		||||
        $sql = _delete::parse($sql, $bindings);
 | 
			
		||||
        $meta = ["isa" => "delete", "type" => "dml"];
 | 
			
		||||
      } elseif (_generic::isa($prefix)) {
 | 
			
		||||
        $sql = _generic::parse($sql, $bindings);
 | 
			
		||||
        $meta = ["isa" => "generic", "type" => null];
 | 
			
		||||
      } else {
 | 
			
		||||
        ## 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);
 | 
			
		||||
        throw ValueException::invalid_kind($sql, "query");
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    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 {
 | 
			
		||||
      if (!is_string($sql)) $sql = strval($sql);
 | 
			
		||||
      if (_create::isa($sql)) {
 | 
			
		||||
        $meta = ["isa" => "create", "type" => "ddl"];
 | 
			
		||||
      } elseif (_select::isa($sql)) {
 | 
			
		||||
        $meta = ["isa" => "select", "type" => "dql"];
 | 
			
		||||
      } elseif (_insert::isa($sql)) {
 | 
			
		||||
        $meta = ["isa" => "insert", "type" => "dml"];
 | 
			
		||||
      } elseif (_update::isa($sql)) {
 | 
			
		||||
        $meta = ["isa" => "update", "type" => "dml"];
 | 
			
		||||
      } elseif (_delete::isa($sql)) {
 | 
			
		||||
        $meta = ["isa" => "delete", "type" => "dml"];
 | 
			
		||||
      } elseif (_generic::isa($sql)) {
 | 
			
		||||
        $meta = ["isa" => "generic", "type" => null];
 | 
			
		||||
      } else {
 | 
			
		||||
        ## 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);
 | 
			
		||||
        $meta = ["isa" => "generic", "type" => null];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $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");
 | 
			
		||||
    }
 | 
			
		||||
  static function with($sql, ?array $params=null): array {
 | 
			
		||||
    static::verifix($sql, $params);
 | 
			
		||||
    return [$sql, $params];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  abstract protected static function verifix(&$sql, ?array &$bindinds=null, ?array &$meta=null): void;
 | 
			
		||||
 | 
			
		||||
  function __construct($sql, ?array $bindings=null) {
 | 
			
		||||
    static::verifix($sql, $bindings, $meta);
 | 
			
		||||
    $this->sql = $sql;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
namespace nulib\db\pdo;
 | 
			
		||||
namespace nulib\db\_private;
 | 
			
		||||
 | 
			
		||||
use nulib\php\nur_func;
 | 
			
		||||
use nulib\db\IDatabase;
 | 
			
		||||
use nulib\php\func;
 | 
			
		||||
 | 
			
		||||
class _config {
 | 
			
		||||
  static function with($configs): self {
 | 
			
		||||
@ -23,13 +24,12 @@ class _config {
 | 
			
		||||
  /** @var array */
 | 
			
		||||
  protected $configs;
 | 
			
		||||
 | 
			
		||||
  function configure(Pdo $pdo): void {
 | 
			
		||||
  function configure(IDatabase $db): void {
 | 
			
		||||
    foreach ($this->configs as $key => $config) {
 | 
			
		||||
      if (is_string($config) && !nur_func::is_method($config)) {
 | 
			
		||||
        $pdo->exec($config);
 | 
			
		||||
      if (is_string($config) && !func::is_method($config)) {
 | 
			
		||||
        $db->exec($config);
 | 
			
		||||
      } else {
 | 
			
		||||
        nur_func::ensure_func($config, $this, $args);
 | 
			
		||||
        nur_func::call($config, $pdo, $key, ...$args);
 | 
			
		||||
        func::with($config)->bind($this, true)->invoke([$db, $key]);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\_private;
 | 
			
		||||
 | 
			
		||||
class _create {
 | 
			
		||||
class _create extends _common {
 | 
			
		||||
  const SCHEMA = [
 | 
			
		||||
    "prefix" => "?string",
 | 
			
		||||
    "table" => "string",
 | 
			
		||||
@ -9,4 +9,46 @@ class _create {
 | 
			
		||||
    "cols" => "?array",
 | 
			
		||||
    "suffix" => "?string",
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  static function isa(string $sql): bool {
 | 
			
		||||
    #XXX implémentation minimale
 | 
			
		||||
    return preg_match("/^create(?:\s+table)?\b/i", $sql);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static function parse(array $query, ?array &$bindings=null): string {
 | 
			
		||||
    #XXX implémentation minimale
 | 
			
		||||
    $tmpsql = self::merge_seq($query);
 | 
			
		||||
    self::consume('create(?:\s+table)?\b', $tmpsql);
 | 
			
		||||
    $sql = ["create table"];
 | 
			
		||||
    if ($tmpsql) $sql[] = $tmpsql;
 | 
			
		||||
 | 
			
		||||
    ## préfixe
 | 
			
		||||
    $prefix = $query["prefix"] ?? null;
 | 
			
		||||
    if ($prefix !== null) $sql[] = $prefix;
 | 
			
		||||
 | 
			
		||||
    ## table
 | 
			
		||||
    $table = $query["table"] ?? null;
 | 
			
		||||
    if ($table !== null) $sql[] = $table;
 | 
			
		||||
 | 
			
		||||
    ## columns
 | 
			
		||||
    $cols = $query["cols"] ?? null;
 | 
			
		||||
    if ($cols !== null) {
 | 
			
		||||
      $index = 0;
 | 
			
		||||
      foreach ($cols as $col => &$definition) {
 | 
			
		||||
        if ($col === $index) {
 | 
			
		||||
          $index++;
 | 
			
		||||
        } else {
 | 
			
		||||
          $definition = "$col $definition";
 | 
			
		||||
        }
 | 
			
		||||
      }; unset($definition);
 | 
			
		||||
      $sql[] = "(\n  ".implode("\n, ", $cols)."\n)";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ## suffixe
 | 
			
		||||
    $suffix = $query["suffix"] ?? null;
 | 
			
		||||
    if ($suffix !== null) $sql[] = $suffix;
 | 
			
		||||
 | 
			
		||||
    ## fin de la requête
 | 
			
		||||
    return implode(" ", $sql);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,48 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\_private;
 | 
			
		||||
 | 
			
		||||
class _delete {
 | 
			
		||||
class _delete extends _common {
 | 
			
		||||
  const SCHEMA = [
 | 
			
		||||
    "prefix" => "?string",
 | 
			
		||||
    "from" => "?string",
 | 
			
		||||
    "where" => "?array",
 | 
			
		||||
    "suffix" => "?string",
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  static function isa(string $sql): bool {
 | 
			
		||||
    return preg_match("/^delete(?:\s+from)?\b/i", $sql);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static function parse(array $query, ?array &$bindings=null): string {
 | 
			
		||||
    #XXX implémentation minimale
 | 
			
		||||
    $tmpsql = self::merge_seq($query);
 | 
			
		||||
    self::consume('delete(?:\s+from)?\b', $tmpsql);
 | 
			
		||||
    $sql = ["delete from"];
 | 
			
		||||
    if ($tmpsql) $sql[] = $tmpsql;
 | 
			
		||||
 | 
			
		||||
    ## préfixe
 | 
			
		||||
    $prefix = $query["prefix"] ?? null;
 | 
			
		||||
    if ($prefix !== null) $sql[] = $prefix;
 | 
			
		||||
 | 
			
		||||
    ## table
 | 
			
		||||
    $from = $query["from"] ?? null;
 | 
			
		||||
    if ($from !== null) $sql[] = $from;
 | 
			
		||||
 | 
			
		||||
    ## where
 | 
			
		||||
    $where = $query["where"] ?? null;
 | 
			
		||||
    if ($where !== null) {
 | 
			
		||||
      self::parse_conds($where, $wheresql, $bindings);
 | 
			
		||||
      if ($wheresql) {
 | 
			
		||||
        $sql[] = "where";
 | 
			
		||||
        $sql[] = implode(" and ", $wheresql);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ## suffixe
 | 
			
		||||
    $suffix = $query["suffix"] ?? null;
 | 
			
		||||
    if ($suffix !== null) $sql[] = $suffix;
 | 
			
		||||
 | 
			
		||||
    ## fin de la requête
 | 
			
		||||
    return implode(" ", $sql);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,21 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\_private;
 | 
			
		||||
 | 
			
		||||
class _generic {
 | 
			
		||||
use nulib\cl;
 | 
			
		||||
use nulib\ValueException;
 | 
			
		||||
 | 
			
		||||
class _generic extends _common {
 | 
			
		||||
  const SCHEMA = [
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  static function isa(string $sql): bool {
 | 
			
		||||
    return preg_match('/^drop\s+table\b/i', $sql);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static function parse(array $query, ?array &$bindings=null): string {
 | 
			
		||||
    if (!cl::is_list($query)) {
 | 
			
		||||
      throw new ValueException("Seuls les tableaux séquentiels sont supportés");
 | 
			
		||||
    }
 | 
			
		||||
    return self::merge_seq($query);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,10 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\_private;
 | 
			
		||||
 | 
			
		||||
class _insert {
 | 
			
		||||
use nulib\cl;
 | 
			
		||||
use nulib\ValueException;
 | 
			
		||||
 | 
			
		||||
class _insert extends _common {
 | 
			
		||||
  const SCHEMA = [
 | 
			
		||||
    "prefix" => "?string",
 | 
			
		||||
    "into" => "?string",
 | 
			
		||||
@ -10,4 +13,79 @@ class _insert {
 | 
			
		||||
    "values" => "?array",
 | 
			
		||||
    "suffix" => "?string",
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  static function isa(string $sql): bool {
 | 
			
		||||
    return preg_match("/^insert\b/i", $sql);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * parser une chaine de la forme
 | 
			
		||||
   * "insert [into] [TABLE] [(COLS)] [values (VALUES)]"
 | 
			
		||||
   */
 | 
			
		||||
  static function parse(array $query, ?array &$bindings=null): string {
 | 
			
		||||
    # fusionner d'abord toutes les parties séquentielles
 | 
			
		||||
    $usersql = $tmpsql = self::merge_seq($query);
 | 
			
		||||
 | 
			
		||||
    ### vérifier la présence des parties nécessaires
 | 
			
		||||
    $sql = [];
 | 
			
		||||
    if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
 | 
			
		||||
 | 
			
		||||
    ## insert
 | 
			
		||||
    self::consume('(insert(?:\s+or\s+(?:ignore|replace))?)\s*', $tmpsql, $ms);
 | 
			
		||||
    $sql[] = $ms[1];
 | 
			
		||||
 | 
			
		||||
    ## into
 | 
			
		||||
    self::consume('into\s*', $tmpsql);
 | 
			
		||||
    $sql[] = "into";
 | 
			
		||||
    $into = $query["into"] ?? null;
 | 
			
		||||
    if (self::consume('([a-z_][a-z0-9_]*)\s*', $tmpsql, $ms)) {
 | 
			
		||||
      if ($into === null) $into = $ms[1];
 | 
			
		||||
      $sql[] = $into;
 | 
			
		||||
    } elseif ($into !== null) {
 | 
			
		||||
      $sql[] = $into;
 | 
			
		||||
    } else {
 | 
			
		||||
      throw new ValueException("expected table name: $usersql");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ## cols & values
 | 
			
		||||
    $usercols = [];
 | 
			
		||||
    $uservalues = [];
 | 
			
		||||
    if (self::consume('\(([^)]*)\)\s*', $tmpsql, $ms)) {
 | 
			
		||||
      $usercols = array_merge($usercols, preg_split("/\s*,\s*/", $ms[1]));
 | 
			
		||||
    }
 | 
			
		||||
    $cols = cl::withn($query["cols"] ?? null);
 | 
			
		||||
    $values = cl::withn($query["values"] ?? null);
 | 
			
		||||
    $schema = $query["schema"] ?? null;
 | 
			
		||||
    if ($cols === null) {
 | 
			
		||||
      if ($usercols) {
 | 
			
		||||
        $cols = $usercols;
 | 
			
		||||
      } elseif ($values) {
 | 
			
		||||
        $cols = array_keys($values);
 | 
			
		||||
        $usercols = array_merge($usercols, $cols);
 | 
			
		||||
      } elseif ($schema && is_array($schema)) {
 | 
			
		||||
        #XXX implémenter support AssocSchema
 | 
			
		||||
        $cols = array_keys($schema);
 | 
			
		||||
        $usercols = array_merge($usercols, $cols);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (self::consume('values\s+\(\s*(.*)\s*\)\s*', $tmpsql, $ms)) {
 | 
			
		||||
      if ($ms[1]) $uservalues[] = $ms[1];
 | 
			
		||||
    }
 | 
			
		||||
    if ($cols !== null && !$uservalues) {
 | 
			
		||||
      if (!$usercols) $usercols = $cols;
 | 
			
		||||
      foreach ($cols as $col) {
 | 
			
		||||
        $uservalues[] = ":$col";
 | 
			
		||||
        $bindings[$col] = $values[$col] ?? null;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $sql[] = "(" . implode(", ", $usercols) . ")";
 | 
			
		||||
    $sql[] = "values (" . implode(", ", $uservalues) . ")";
 | 
			
		||||
 | 
			
		||||
    ## suffixe
 | 
			
		||||
    if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
 | 
			
		||||
 | 
			
		||||
    ## fin de la requête
 | 
			
		||||
    self::check_eof($tmpsql, $usersql);
 | 
			
		||||
    return implode(" ", $sql);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										72
									
								
								php/src/db/_private/_migration.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								php/src/db/_private/_migration.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,72 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\_private;
 | 
			
		||||
 | 
			
		||||
use nulib\db\IDatabase;
 | 
			
		||||
use nulib\php\func;
 | 
			
		||||
 | 
			
		||||
abstract class _migration {
 | 
			
		||||
  const MIGRATION = null;
 | 
			
		||||
 | 
			
		||||
  function __construct($migrations, string $channel="", ?IDatabase $db=null) {
 | 
			
		||||
    $this->db = $db;
 | 
			
		||||
    $this->channel = $channel;
 | 
			
		||||
    if ($migrations === null) $migrations = static::MIGRATION;
 | 
			
		||||
    if ($migrations === null) $migrations = [];
 | 
			
		||||
    elseif (is_string($migrations)) $migrations = [$migrations];
 | 
			
		||||
    elseif (is_callable($migrations)) $migrations = [$migrations];
 | 
			
		||||
    elseif (!is_array($migrations)) $migrations = [strval($migrations)];
 | 
			
		||||
    $this->migrations = $migrations;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected ?IDatabase $db;
 | 
			
		||||
 | 
			
		||||
  protected string $channel;
 | 
			
		||||
 | 
			
		||||
  const MIGRATION_TABLE = "_migration";
 | 
			
		||||
  const MIGRATION_COLS = [
 | 
			
		||||
    "channel" => "varchar not null",
 | 
			
		||||
    "name" => "varchar not null",
 | 
			
		||||
    "done" => "integer not null default 0",
 | 
			
		||||
    "primary key (channel, name)",
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  protected function ensureTable(): void {
 | 
			
		||||
    $this->db->exec([
 | 
			
		||||
      "create table if not exists",
 | 
			
		||||
      "table" => static::MIGRATION_TABLE,
 | 
			
		||||
      "cols" => static::MIGRATION_COLS,
 | 
			
		||||
    ]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function isMigrated(string $name): bool {
 | 
			
		||||
    return boolval($this->db->get([
 | 
			
		||||
      "select 1",
 | 
			
		||||
      "from" => static::MIGRATION_TABLE,
 | 
			
		||||
      "where" => [
 | 
			
		||||
        "channel" => $this->channel,
 | 
			
		||||
        "name" => $name,
 | 
			
		||||
        "done" => 1,
 | 
			
		||||
      ],
 | 
			
		||||
    ]));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  abstract protected function setMigrated(string $name, bool $done): void;
 | 
			
		||||
 | 
			
		||||
  /** @var callable[]|string[]  */
 | 
			
		||||
  protected $migrations;
 | 
			
		||||
 | 
			
		||||
  function migrate(?IDatabase $db=null): void {
 | 
			
		||||
    $db = ($this->db ??= $db);
 | 
			
		||||
    $this->ensureTable();
 | 
			
		||||
    foreach ($this->migrations as $name => $migration) {
 | 
			
		||||
      if ($this->isMigrated($name)) continue;
 | 
			
		||||
      $this->setMigrated($name, false);
 | 
			
		||||
      if (is_string($migration) || !func::is_callable($migration)) {
 | 
			
		||||
        $db->exec($migration);
 | 
			
		||||
      } else {
 | 
			
		||||
        func::with($migration)->bind($this, true)->invoke([$db, $name]);
 | 
			
		||||
      }
 | 
			
		||||
      $this->setMigrated($name, true);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,7 +1,11 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\_private;
 | 
			
		||||
 | 
			
		||||
class _select {
 | 
			
		||||
use nulib\cl;
 | 
			
		||||
use nulib\str;
 | 
			
		||||
use nulib\ValueException;
 | 
			
		||||
 | 
			
		||||
class _select extends _common {
 | 
			
		||||
  const SCHEMA = [
 | 
			
		||||
    "prefix" => "?string",
 | 
			
		||||
    "schema" => "?array",
 | 
			
		||||
@ -14,4 +18,164 @@ class _select {
 | 
			
		||||
    "having" => "?array",
 | 
			
		||||
    "suffix" => "?string",
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  static function isa(string $sql): bool {
 | 
			
		||||
    return preg_match("/^select\b/i", $sql);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static function add_prefix(string $col, ?string $prefix): string {
 | 
			
		||||
    if ($prefix === null) return $col;
 | 
			
		||||
    if (strpos($col, ".") !== false) return $col;
 | 
			
		||||
    return "$prefix$col";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * parser une chaine de la forme
 | 
			
		||||
   * "select [COLS] [from TABLE] [where CONDS] [order by ORDERS] [group by GROUPS] [having CONDS]"
 | 
			
		||||
   */
 | 
			
		||||
  static function parse(array $query, ?array &$bindings=null): string {
 | 
			
		||||
    # fusionner d'abord toutes les parties séquentielles
 | 
			
		||||
    $usersql = $tmpsql = self::merge_seq($query);
 | 
			
		||||
 | 
			
		||||
    ### vérifier la présence des parties nécessaires
 | 
			
		||||
    $sql = [];
 | 
			
		||||
 | 
			
		||||
    ## préfixe
 | 
			
		||||
    if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
 | 
			
		||||
 | 
			
		||||
    ## select
 | 
			
		||||
    self::consume('(select(?:\s*distinct)?)\s*', $tmpsql, $ms);
 | 
			
		||||
    $sql[] = $ms[1];
 | 
			
		||||
 | 
			
		||||
    ## cols
 | 
			
		||||
    $usercols = [];
 | 
			
		||||
    if (self::consume('(.*?)\s*(?=$|\bfrom\b)', $tmpsql, $ms)) {
 | 
			
		||||
      if ($ms[1]) $usercols[] = $ms[1];
 | 
			
		||||
    }
 | 
			
		||||
    $colPrefix = $query["col_prefix"] ?? null;
 | 
			
		||||
    if ($colPrefix !== null) str::add_suffix($colPrefix, ".");
 | 
			
		||||
    $tmpcols = cl::withn($query["cols"] ?? null);
 | 
			
		||||
    $schema = $query["schema"] ?? null;
 | 
			
		||||
    if ($tmpcols !== null) {
 | 
			
		||||
      $cols = [];
 | 
			
		||||
      $index = 0;
 | 
			
		||||
      foreach ($tmpcols as $key => $col) {
 | 
			
		||||
        if ($key === $index) {
 | 
			
		||||
          $index++;
 | 
			
		||||
          $cols[] = $col;
 | 
			
		||||
          $usercols[] = self::add_prefix($col, $colPrefix);
 | 
			
		||||
        } else {
 | 
			
		||||
          $cols[] = $key;
 | 
			
		||||
          $usercols[] = self::add_prefix($col, $colPrefix)." as $key";
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      $cols = null;
 | 
			
		||||
      if ($schema && is_array($schema) && !in_array("*", $usercols)) {
 | 
			
		||||
        $cols = array_keys($schema);
 | 
			
		||||
        foreach ($cols as $col) {
 | 
			
		||||
          $usercols[] = self::add_prefix($col, $colPrefix);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (!$usercols && !$cols) $usercols = [self::add_prefix("*", $colPrefix)];
 | 
			
		||||
    $sql[] = implode(", ", $usercols);
 | 
			
		||||
 | 
			
		||||
    ## from
 | 
			
		||||
    $from = $query["from"] ?? null;
 | 
			
		||||
    if (self::consume('from\s+([a-z_][a-z0-9_]*)\s*(?=;?\s*$|\bwhere\b)', $tmpsql, $ms)) {
 | 
			
		||||
      if ($from === null) $from = $ms[1];
 | 
			
		||||
      $sql[] = "from";
 | 
			
		||||
      $sql[] = $from;
 | 
			
		||||
    } elseif ($from !== null) {
 | 
			
		||||
      $sql[] = "from";
 | 
			
		||||
      $sql[] = $from;
 | 
			
		||||
    } else {
 | 
			
		||||
      throw new ValueException("expected table name: $usersql");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ## where
 | 
			
		||||
    $userwhere = [];
 | 
			
		||||
    if (self::consume('where\b\s*(.*?)(?=;?\s*$|\border\s+by\b)', $tmpsql, $ms)) {
 | 
			
		||||
      if ($ms[1]) $userwhere[] = $ms[1];
 | 
			
		||||
    }
 | 
			
		||||
    $where = cl::withn($query["where"] ?? null);
 | 
			
		||||
    if ($where !== null) self::parse_conds($where, $userwhere, $bindings);
 | 
			
		||||
    if ($userwhere) {
 | 
			
		||||
      $sql[] = "where";
 | 
			
		||||
      $sql[] = implode(" and ", $userwhere);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ## order by
 | 
			
		||||
    $userorderby = [];
 | 
			
		||||
    if (self::consume('order\s+by\b\s*(.*?)(?=;?\s*$|\bgroup\s+by\b)', $tmpsql, $ms)) {
 | 
			
		||||
      if ($ms[1]) $userorderby[] = $ms[1];
 | 
			
		||||
    }
 | 
			
		||||
    $orderby = cl::withn($query["order by"] ?? null);
 | 
			
		||||
    if ($orderby !== null) {
 | 
			
		||||
      $index = 0;
 | 
			
		||||
      foreach ($orderby as $key => $value) {
 | 
			
		||||
        if ($key === $index) {
 | 
			
		||||
          $userorderby[] = $value;
 | 
			
		||||
          $index++;
 | 
			
		||||
        } else {
 | 
			
		||||
          if ($value === null) $value = false;
 | 
			
		||||
          if (!is_bool($value)) {
 | 
			
		||||
            $userorderby[] = "$key $value";
 | 
			
		||||
          } elseif ($value) {
 | 
			
		||||
            $userorderby[] = $key;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if ($userorderby) {
 | 
			
		||||
      $sql[] = "order by";
 | 
			
		||||
      $sql[] = implode(", ", $userorderby);
 | 
			
		||||
    }
 | 
			
		||||
    ## group by
 | 
			
		||||
    $usergroupby = [];
 | 
			
		||||
    if (self::consume('group\s+by\b\s*(.*?)(?=;?\s*$|\bhaving\b)', $tmpsql, $ms)) {
 | 
			
		||||
      if ($ms[1]) $usergroupby[] = $ms[1];
 | 
			
		||||
    }
 | 
			
		||||
    $groupby = cl::withn($query["group by"] ?? null);
 | 
			
		||||
    if ($groupby !== null) {
 | 
			
		||||
      $index = 0;
 | 
			
		||||
      foreach ($groupby as $key => $value) {
 | 
			
		||||
        if ($key === $index) {
 | 
			
		||||
          $usergroupby[] = $value;
 | 
			
		||||
          $index++;
 | 
			
		||||
        } else {
 | 
			
		||||
          if ($value === null) $value = false;
 | 
			
		||||
          if (!is_bool($value)) {
 | 
			
		||||
            $usergroupby[] = "$key $value";
 | 
			
		||||
          } elseif ($value) {
 | 
			
		||||
            $usergroupby[] = $key;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if ($usergroupby) {
 | 
			
		||||
      $sql[] = "group by";
 | 
			
		||||
      $sql[] = implode(", ", $usergroupby);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ## having
 | 
			
		||||
    $userhaving = [];
 | 
			
		||||
    if (self::consume('having\b\s*(.*?)(?=;?\s*$)', $tmpsql, $ms)) {
 | 
			
		||||
      if ($ms[1]) $userhaving[] = $ms[1];
 | 
			
		||||
    }
 | 
			
		||||
    $having = cl::withn($query["having"] ?? null);
 | 
			
		||||
    if ($having !== null) self::parse_conds($having, $userhaving, $bindings);
 | 
			
		||||
    if ($userhaving) {
 | 
			
		||||
      $sql[] = "having";
 | 
			
		||||
      $sql[] = implode(" and ", $userhaving);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ## suffixe
 | 
			
		||||
    if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
 | 
			
		||||
 | 
			
		||||
    ## fin de la requête
 | 
			
		||||
    self::check_eof($tmpsql, $usersql);
 | 
			
		||||
    return implode(" ", $sql);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\_private;
 | 
			
		||||
 | 
			
		||||
class _update {
 | 
			
		||||
class _update extends _common {
 | 
			
		||||
  const SCHEMA = [
 | 
			
		||||
    "prefix" => "?string",
 | 
			
		||||
    "table" => "?string",
 | 
			
		||||
@ -11,4 +11,43 @@ class _update {
 | 
			
		||||
    "where" => "?array",
 | 
			
		||||
    "suffix" => "?string",
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  static function isa(string $sql): bool {
 | 
			
		||||
    return preg_match("/^update\b/i", $sql);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static function parse(array $query, ?array &$bindings=null): string {
 | 
			
		||||
    #XXX implémentation minimale
 | 
			
		||||
    $sql = [self::merge_seq($query)];
 | 
			
		||||
 | 
			
		||||
    ## préfixe
 | 
			
		||||
    $prefix = $query["prefix"] ?? null;
 | 
			
		||||
    if ($prefix !== null) $sql[] = $prefix;
 | 
			
		||||
 | 
			
		||||
    ## table
 | 
			
		||||
    $table = $query["table"] ?? null;
 | 
			
		||||
    if ($table !== null) $sql[] = $table;
 | 
			
		||||
 | 
			
		||||
    ## set
 | 
			
		||||
    self::parse_set_values($query["values"], $setsql, $bindings);
 | 
			
		||||
    $sql[] = "set";
 | 
			
		||||
    $sql[] = implode(", ", $setsql);
 | 
			
		||||
 | 
			
		||||
    ## where
 | 
			
		||||
    $where = $query["where"] ?? null;
 | 
			
		||||
    if ($where !== null) {
 | 
			
		||||
      self::parse_conds($where, $wheresql, $bindings);
 | 
			
		||||
      if ($wheresql) {
 | 
			
		||||
        $sql[] = "where";
 | 
			
		||||
        $sql[] = implode(" and ", $wheresql);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ## suffixe
 | 
			
		||||
    $suffix = $query["suffix"] ?? null;
 | 
			
		||||
    if ($suffix !== null) $sql[] = $suffix;
 | 
			
		||||
 | 
			
		||||
    ## fin de la requête
 | 
			
		||||
    return implode(" ", $sql);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\mysql;
 | 
			
		||||
 | 
			
		||||
use nulib\cl;
 | 
			
		||||
use nulib\db\CapacitorChannel;
 | 
			
		||||
use nulib\db\CapacitorStorage;
 | 
			
		||||
 | 
			
		||||
@ -12,8 +13,7 @@ class MysqlStorage extends CapacitorStorage {
 | 
			
		||||
    $this->db = Mysql::with($mysql);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @var Mysql */
 | 
			
		||||
  protected $db;
 | 
			
		||||
  protected Mysql $db;
 | 
			
		||||
 | 
			
		||||
  function db(): Mysql {
 | 
			
		||||
    return $this->db;
 | 
			
		||||
@ -23,17 +23,35 @@ class MysqlStorage extends CapacitorStorage {
 | 
			
		||||
    "id_" => "integer primary key auto_increment",
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  function _getMigration(CapacitorChannel $channel): _mysqlMigration {
 | 
			
		||||
    return new _mysqlMigration(cl::merge([
 | 
			
		||||
      $this->_createSql($channel),
 | 
			
		||||
    ], $channel->getMigration()), $channel->getName());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function _getCreateSql(CapacitorChannel $channel): string {
 | 
			
		||||
    $query = new _query_base($this->_createSql($channel));
 | 
			
		||||
    $query = new _mysqlQuery($this->_createSql($channel));
 | 
			
		||||
    return self::format_sql($channel, $query->getSql());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const CHANNELS_COLS = [
 | 
			
		||||
    "name" => "varchar(255) primary key",
 | 
			
		||||
    "table_name" => "varchar(64)",
 | 
			
		||||
    "class_name" => "varchar(255)",
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  protected function _addToChannelsSql(CapacitorChannel $channel): array {
 | 
			
		||||
    return cl::merge(parent::_addToChannelsSql($channel), [
 | 
			
		||||
      "suffix" => "on duplicate key update name = name",
 | 
			
		||||
    ]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function _exists(CapacitorChannel $channel): bool {
 | 
			
		||||
    $db = $this->db;
 | 
			
		||||
    $tableName = $db->get([
 | 
			
		||||
    $mysql = $this->db;
 | 
			
		||||
    $tableName = $mysql->get([
 | 
			
		||||
      "select table_name from information_schema.tables",
 | 
			
		||||
      "where" => [
 | 
			
		||||
        "table_schema" => $db->getDbname(),
 | 
			
		||||
        "table_schema" => $mysql->getDbname(),
 | 
			
		||||
        "table_name" => $channel->getTableName(),
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										31
									
								
								php/src/db/mysql/_mysqlMigration.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								php/src/db/mysql/_mysqlMigration.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\mysql;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_migration;
 | 
			
		||||
 | 
			
		||||
class _mysqlMigration extends _migration {
 | 
			
		||||
  static function with($migration): self {
 | 
			
		||||
    if ($migration instanceof self) return $migration;
 | 
			
		||||
    else return new static($migration);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const MIGRATION_COLS = [
 | 
			
		||||
    "channel" => "varchar(64) not null",
 | 
			
		||||
    "name" => "varchar(64) not null",
 | 
			
		||||
    "done" => "integer not null default 0",
 | 
			
		||||
    "primary key (channel, name)",
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  protected function setMigrated(string $name, bool $done): void {
 | 
			
		||||
    $this->db->exec([
 | 
			
		||||
      "insert",
 | 
			
		||||
      "into" => static::MIGRATION_TABLE,
 | 
			
		||||
      "values" => [
 | 
			
		||||
        "channel" => $this->channel,
 | 
			
		||||
        "name" => $name,
 | 
			
		||||
        "done" => $done? 1: 0,
 | 
			
		||||
      ],
 | 
			
		||||
      "suffix" => "on duplicate key update done = :done",
 | 
			
		||||
    ]);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								php/src/db/mysql/_mysqlQuery.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								php/src/db/mysql/_mysqlQuery.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\mysql;
 | 
			
		||||
 | 
			
		||||
use nulib\db\pdo\_pdoQuery;
 | 
			
		||||
 | 
			
		||||
class _mysqlQuery extends _pdoQuery {
 | 
			
		||||
  const DEBUG_QUERIES = false;
 | 
			
		||||
}
 | 
			
		||||
@ -1,52 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\mysql;
 | 
			
		||||
 | 
			
		||||
use nulib\ValueException;
 | 
			
		||||
 | 
			
		||||
class _query_base extends \nulib\db\pdo\_query_base {
 | 
			
		||||
  protected static function verifix(&$sql, ?array &$bindinds=null, ?array &$meta=null): void {
 | 
			
		||||
    if (is_array($sql)) {
 | 
			
		||||
      $prefix = $sql[0] ?? null;
 | 
			
		||||
      if ($prefix === null) {
 | 
			
		||||
        throw new ValueException("requête invalide");
 | 
			
		||||
      } elseif (_query_create::isa($prefix)) {
 | 
			
		||||
        $sql = _query_create::parse($sql, $bindinds);
 | 
			
		||||
        $meta = ["isa" => "create", "type" => "ddl"];
 | 
			
		||||
      } elseif (_query_select::isa($prefix)) {
 | 
			
		||||
        $sql = _query_select::parse($sql, $bindinds);
 | 
			
		||||
        $meta = ["isa" => "select", "type" => "dql"];
 | 
			
		||||
      } elseif (_query_insert::isa($prefix)) {
 | 
			
		||||
        $sql = _query_insert::parse($sql, $bindinds);
 | 
			
		||||
        $meta = ["isa" => "insert", "type" => "dml"];
 | 
			
		||||
      } elseif (_query_update::isa($prefix)) {
 | 
			
		||||
        $sql = _query_update::parse($sql, $bindinds);
 | 
			
		||||
        $meta = ["isa" => "update", "type" => "dml"];
 | 
			
		||||
      } elseif (_query_delete::isa($prefix)) {
 | 
			
		||||
        $sql = _query_delete::parse($sql, $bindinds);
 | 
			
		||||
        $meta = ["isa" => "delete", "type" => "dml"];
 | 
			
		||||
      } elseif (_query_generic::isa($prefix)) {
 | 
			
		||||
        $sql = _query_generic::parse($sql, $bindinds);
 | 
			
		||||
        $meta = ["isa" => "generic", "type" => null];
 | 
			
		||||
      } else {
 | 
			
		||||
        throw ValueException::invalid_kind($sql, "query");
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      if (!is_string($sql)) $sql = strval($sql);
 | 
			
		||||
      if (_query_create::isa($sql)) {
 | 
			
		||||
        $meta = ["isa" => "create", "type" => "ddl"];
 | 
			
		||||
      } elseif (_query_select::isa($sql)) {
 | 
			
		||||
        $meta = ["isa" => "select", "type" => "dql"];
 | 
			
		||||
      } elseif (_query_insert::isa($sql)) {
 | 
			
		||||
        $meta = ["isa" => "insert", "type" => "dml"];
 | 
			
		||||
      } elseif (_query_update::isa($sql)) {
 | 
			
		||||
        $meta = ["isa" => "update", "type" => "dml"];
 | 
			
		||||
      } elseif (_query_delete::isa($sql)) {
 | 
			
		||||
        $meta = ["isa" => "delete", "type" => "dml"];
 | 
			
		||||
      } elseif (_query_generic::isa($sql)) {
 | 
			
		||||
        $meta = ["isa" => "generic", "type" => null];
 | 
			
		||||
      } else {
 | 
			
		||||
        $meta = ["isa" => "generic", "type" => null];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\mysql;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_create;
 | 
			
		||||
use nulib\db\_private\Tcreate;
 | 
			
		||||
 | 
			
		||||
class _query_create extends _query_base {
 | 
			
		||||
  use Tcreate;
 | 
			
		||||
  const SCHEMA = _create::SCHEMA;
 | 
			
		||||
}
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\mysql;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_delete;
 | 
			
		||||
use nulib\db\_private\Tdelete;
 | 
			
		||||
 | 
			
		||||
class _query_delete extends _query_base {
 | 
			
		||||
  use Tdelete;
 | 
			
		||||
  const SCHEMA = _delete::SCHEMA;
 | 
			
		||||
}
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\mysql;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_generic;
 | 
			
		||||
use nulib\db\_private\Tgeneric;
 | 
			
		||||
 | 
			
		||||
class _query_generic extends _query_base {
 | 
			
		||||
  use Tgeneric;
 | 
			
		||||
  const SCHEMA = _generic::SCHEMA;
 | 
			
		||||
}
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\mysql;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_insert;
 | 
			
		||||
use nulib\db\_private\Tinsert;
 | 
			
		||||
 | 
			
		||||
class _query_insert extends _query_base {
 | 
			
		||||
  use Tinsert;
 | 
			
		||||
  const SCHEMA = _insert::SCHEMA;
 | 
			
		||||
}
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\mysql;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_select;
 | 
			
		||||
use nulib\db\_private\Tselect;
 | 
			
		||||
 | 
			
		||||
class _query_select extends _query_base {
 | 
			
		||||
  use Tselect;
 | 
			
		||||
  const SCHEMA = _select::SCHEMA;
 | 
			
		||||
}
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\mysql;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_update;
 | 
			
		||||
use nulib\db\_private\Tupdate;
 | 
			
		||||
 | 
			
		||||
class _query_update extends _query_base {
 | 
			
		||||
  use Tupdate;
 | 
			
		||||
  const SCHEMA = _update::SCHEMA;
 | 
			
		||||
}
 | 
			
		||||
@ -1,12 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\mysql;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class query: classe outil temporaire pour générer les requêtes
 | 
			
		||||
 */
 | 
			
		||||
class query extends _query_base {
 | 
			
		||||
  static function with($sql, ?array $params=null): array {
 | 
			
		||||
    self::verifix($sql, $params);
 | 
			
		||||
    return [$sql, $params];
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -3,10 +3,11 @@ namespace nulib\db\pdo;
 | 
			
		||||
 | 
			
		||||
use Generator;
 | 
			
		||||
use nulib\cl;
 | 
			
		||||
use nulib\db\_private\_config;
 | 
			
		||||
use nulib\db\_private\Tvalues;
 | 
			
		||||
use nulib\db\IDatabase;
 | 
			
		||||
use nulib\db\ITransactor;
 | 
			
		||||
use nulib\php\nur_func;
 | 
			
		||||
use nulib\php\func;
 | 
			
		||||
use nulib\ValueException;
 | 
			
		||||
 | 
			
		||||
class Pdo implements IDatabase {
 | 
			
		||||
@ -21,7 +22,7 @@ class Pdo implements IDatabase {
 | 
			
		||||
        "dbconn" => $pdo->dbconn,
 | 
			
		||||
        "options" => $pdo->options,
 | 
			
		||||
        "config" => $pdo->config,
 | 
			
		||||
        "migrate" => $pdo->migration,
 | 
			
		||||
        "migration" => $pdo->migration,
 | 
			
		||||
      ], $params));
 | 
			
		||||
    } else {
 | 
			
		||||
      return new static($pdo, $params);
 | 
			
		||||
@ -49,7 +50,7 @@ class Pdo implements IDatabase {
 | 
			
		||||
 | 
			
		||||
  protected const CONFIG = null;
 | 
			
		||||
 | 
			
		||||
  protected const MIGRATE = null;
 | 
			
		||||
  protected const MIGRATION = null;
 | 
			
		||||
 | 
			
		||||
  const dbconn_SCHEMA = [
 | 
			
		||||
    "name" => "string",
 | 
			
		||||
@ -62,7 +63,7 @@ class Pdo implements IDatabase {
 | 
			
		||||
    "options" => ["?array|callable"],
 | 
			
		||||
    "replace_config" => ["?array|callable"],
 | 
			
		||||
    "config" => ["?array|callable"],
 | 
			
		||||
    "migrate" => ["?array|string|callable"],
 | 
			
		||||
    "migration" => ["?array|string|callable"],
 | 
			
		||||
    "auto_open" => ["bool", true],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
@ -93,7 +94,7 @@ class Pdo implements IDatabase {
 | 
			
		||||
    }
 | 
			
		||||
    $this->config = $config;
 | 
			
		||||
    # migrations
 | 
			
		||||
    $this->migration = $params["migrate"] ?? static::MIGRATE;
 | 
			
		||||
    $this->migration = $params["migration"] ?? static::MIGRATION;
 | 
			
		||||
    #
 | 
			
		||||
    $defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
 | 
			
		||||
    if ($params["auto_open"] ?? $defaultAutoOpen) {
 | 
			
		||||
@ -104,7 +105,7 @@ class Pdo implements IDatabase {
 | 
			
		||||
  protected ?array $dbconn;
 | 
			
		||||
 | 
			
		||||
  /** @var array|callable */
 | 
			
		||||
  protected array $options;
 | 
			
		||||
  protected $options;
 | 
			
		||||
 | 
			
		||||
  /** @var array|string|callable */
 | 
			
		||||
  protected $config;
 | 
			
		||||
@ -119,8 +120,7 @@ class Pdo implements IDatabase {
 | 
			
		||||
      $dbconn = $this->dbconn;
 | 
			
		||||
      $options = $this->options;
 | 
			
		||||
      if (is_callable($options)) {
 | 
			
		||||
        nur_func::ensure_func($options, $this, $args);
 | 
			
		||||
        $options = nur_func::call($options, ...$args);
 | 
			
		||||
        $options = func::with($options)->bind($this, true)->invoke();
 | 
			
		||||
      }
 | 
			
		||||
      $this->db = new \PDO($dbconn["name"], $dbconn["user"], $dbconn["pass"], $options);
 | 
			
		||||
      _config::with($this->config)->configure($this);
 | 
			
		||||
@ -143,21 +143,16 @@ class Pdo implements IDatabase {
 | 
			
		||||
    return $this->db()->exec($query);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static function is_insert(?string $sql): bool {
 | 
			
		||||
    if ($sql === null) return false;
 | 
			
		||||
    return preg_match('/^\s*insert\b/i', $sql);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function exec($query, ?array $params=null) {
 | 
			
		||||
    $db = $this->db();
 | 
			
		||||
    $query = new _query_base($query, $params);
 | 
			
		||||
    if ($query->useStmt($db, $stmt, $sql)) {
 | 
			
		||||
    $query = new _pdoQuery($query, $params);
 | 
			
		||||
    if ($query->_use_stmt($db, $stmt, $sql)) {
 | 
			
		||||
      if ($stmt->execute() === false) return false;
 | 
			
		||||
      if ($query->isInsert()) return $db->lastInsertId();
 | 
			
		||||
      else return $stmt->rowCount();
 | 
			
		||||
    } else {
 | 
			
		||||
      $rowCount = $db->exec($sql);
 | 
			
		||||
      if (self::is_insert($sql)) return $db->lastInsertId();
 | 
			
		||||
      if ($query->isInsert()) return $db->lastInsertId();
 | 
			
		||||
      else return $rowCount;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -191,7 +186,7 @@ class Pdo implements IDatabase {
 | 
			
		||||
    if ($func !== null) {
 | 
			
		||||
      $commited = false;
 | 
			
		||||
      try {
 | 
			
		||||
        nur_func::call($func, $this);
 | 
			
		||||
        func::call($func, $this);
 | 
			
		||||
        if ($commit) {
 | 
			
		||||
          $this->commit();
 | 
			
		||||
          $commited = true;
 | 
			
		||||
@ -222,11 +217,11 @@ class Pdo implements IDatabase {
 | 
			
		||||
 | 
			
		||||
  function get($query, ?array $params=null, bool $entireRow=false) {
 | 
			
		||||
    $db = $this->db();
 | 
			
		||||
    $query = new _query_base($query, $params);
 | 
			
		||||
    $query = new _pdoQuery($query, $params);
 | 
			
		||||
    $stmt = null;
 | 
			
		||||
    try {
 | 
			
		||||
      /** @var \PDOStatement $stmt */
 | 
			
		||||
      if ($query->useStmt($db, $stmt, $sql)) {
 | 
			
		||||
      if ($query->_use_stmt($db, $stmt, $sql)) {
 | 
			
		||||
        if ($stmt->execute() === false) return null;
 | 
			
		||||
      } else {
 | 
			
		||||
        $stmt = $db->query($sql);
 | 
			
		||||
@ -245,22 +240,18 @@ class Pdo implements IDatabase {
 | 
			
		||||
    return $this->get($query, $params, true);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * si $primaryKeys est fourni, le résultat est indexé sur la(es) colonne(s)
 | 
			
		||||
   * spécifiée(s)
 | 
			
		||||
   */
 | 
			
		||||
  function all($query, ?array $params=null, $primaryKeys=null): Generator {
 | 
			
		||||
  function all($query, ?array $params=null, $primaryKeys=null): iterable {
 | 
			
		||||
    $db = $this->db();
 | 
			
		||||
    $query = new _query_base($query, $params);
 | 
			
		||||
    $query = new _pdoQuery($query, $params);
 | 
			
		||||
    $stmt = null;
 | 
			
		||||
    try {
 | 
			
		||||
      /** @var \PDOStatement $stmt */
 | 
			
		||||
      if ($query->useStmt($db, $stmt, $sql)) {
 | 
			
		||||
      if ($query->_use_stmt($db, $stmt, $sql)) {
 | 
			
		||||
        if ($stmt->execute() === false) return;
 | 
			
		||||
      } else {
 | 
			
		||||
        $stmt = $db->query($sql);
 | 
			
		||||
      }
 | 
			
		||||
      if ($primaryKeys !== null) $primaryKeys = cl::with($primaryKeys);
 | 
			
		||||
      $primaryKeys = cl::withn($primaryKeys);
 | 
			
		||||
      while (($row = $stmt->fetch(\PDO::FETCH_ASSOC)) !== false) {
 | 
			
		||||
        $this->verifixRow($row);
 | 
			
		||||
        if ($primaryKeys !== null) {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										30
									
								
								php/src/db/pdo/_pdoQuery.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								php/src/db/pdo/_pdoQuery.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\pdo;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_base;
 | 
			
		||||
use nulib\db\_private\Tbindings;
 | 
			
		||||
use nulib\output\msg;
 | 
			
		||||
 | 
			
		||||
class _pdoQuery extends _base {
 | 
			
		||||
  use Tbindings;
 | 
			
		||||
 | 
			
		||||
  const DEBUG_QUERIES = false;
 | 
			
		||||
 | 
			
		||||
  function _use_stmt(\PDO $db, ?\PDOStatement &$stmt=null, ?string &$sql=null): bool {
 | 
			
		||||
    if (static::DEBUG_QUERIES) {#XXX
 | 
			
		||||
      msg::info($this->sql);
 | 
			
		||||
      //msg::info(var_export($this->bindings, true));
 | 
			
		||||
    }
 | 
			
		||||
    if ($this->bindings !== null) {
 | 
			
		||||
      $stmt = $db->prepare($this->sql);
 | 
			
		||||
      foreach ($this->bindings as $name => $value) {
 | 
			
		||||
        $this->verifixBindings($value);
 | 
			
		||||
        $stmt->bindValue($name, $value);
 | 
			
		||||
      }
 | 
			
		||||
      return true;
 | 
			
		||||
    } else {
 | 
			
		||||
      $sql = $this->sql;
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,76 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\pdo;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_base;
 | 
			
		||||
use nulib\db\_private\Tbindings;
 | 
			
		||||
use nulib\ValueException;
 | 
			
		||||
 | 
			
		||||
class _query_base extends _base {
 | 
			
		||||
  use Tbindings;
 | 
			
		||||
 | 
			
		||||
  protected static function verifix(&$sql, ?array &$bindinds=null, ?array &$meta=null): void {
 | 
			
		||||
    if (is_array($sql)) {
 | 
			
		||||
      $prefix = $sql[0] ?? null;
 | 
			
		||||
      if ($prefix === null) {
 | 
			
		||||
        throw new ValueException("requête invalide");
 | 
			
		||||
      } elseif (_query_create::isa($prefix)) {
 | 
			
		||||
        $sql = _query_create::parse($sql, $bindinds);
 | 
			
		||||
        $meta = ["isa" => "create", "type" => "ddl"];
 | 
			
		||||
      } elseif (_query_select::isa($prefix)) {
 | 
			
		||||
        $sql = _query_select::parse($sql, $bindinds);
 | 
			
		||||
        $meta = ["isa" => "select", "type" => "dql"];
 | 
			
		||||
      } elseif (_query_insert::isa($prefix)) {
 | 
			
		||||
        $sql = _query_insert::parse($sql, $bindinds);
 | 
			
		||||
        $meta = ["isa" => "insert", "type" => "dml"];
 | 
			
		||||
      } elseif (_query_update::isa($prefix)) {
 | 
			
		||||
        $sql = _query_update::parse($sql, $bindinds);
 | 
			
		||||
        $meta = ["isa" => "update", "type" => "dml"];
 | 
			
		||||
      } elseif (_query_delete::isa($prefix)) {
 | 
			
		||||
        $sql = _query_delete::parse($sql, $bindinds);
 | 
			
		||||
        $meta = ["isa" => "delete", "type" => "dml"];
 | 
			
		||||
      } elseif (_query_generic::isa($prefix)) {
 | 
			
		||||
        $sql = _query_generic::parse($sql, $bindinds);
 | 
			
		||||
        $meta = ["isa" => "generic", "type" => null];
 | 
			
		||||
      } else {
 | 
			
		||||
        throw ValueException::invalid_kind($sql, "query");
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      if (!is_string($sql)) $sql = strval($sql);
 | 
			
		||||
      if (_query_create::isa($sql)) {
 | 
			
		||||
        $meta = ["isa" => "create", "type" => "ddl"];
 | 
			
		||||
      } elseif (_query_select::isa($sql)) {
 | 
			
		||||
        $meta = ["isa" => "select", "type" => "dql"];
 | 
			
		||||
      } elseif (_query_insert::isa($sql)) {
 | 
			
		||||
        $meta = ["isa" => "insert", "type" => "dml"];
 | 
			
		||||
      } elseif (_query_update::isa($sql)) {
 | 
			
		||||
        $meta = ["isa" => "update", "type" => "dml"];
 | 
			
		||||
      } elseif (_query_delete::isa($sql)) {
 | 
			
		||||
        $meta = ["isa" => "delete", "type" => "dml"];
 | 
			
		||||
      } elseif (_query_generic::isa($sql)) {
 | 
			
		||||
        $meta = ["isa" => "generic", "type" => null];
 | 
			
		||||
      } else {
 | 
			
		||||
        $meta = ["isa" => "generic", "type" => null];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const DEBUG_QUERIES = false;
 | 
			
		||||
 | 
			
		||||
  function useStmt(\PDO $db, ?\PDOStatement &$stmt=null, ?string &$sql=null): bool {
 | 
			
		||||
    if (static::DEBUG_QUERIES) { #XXX
 | 
			
		||||
      error_log($this->sql);
 | 
			
		||||
      //error_log(var_export($this->bindings, true));
 | 
			
		||||
    }
 | 
			
		||||
    if ($this->bindings !== null) {
 | 
			
		||||
      $stmt = $db->prepare($this->sql);
 | 
			
		||||
      foreach ($this->bindings as $name => $value) {
 | 
			
		||||
        $this->verifixBindings($value);
 | 
			
		||||
        $stmt->bindValue($name, $value);
 | 
			
		||||
      }
 | 
			
		||||
      return true;
 | 
			
		||||
    } else {
 | 
			
		||||
      $sql = $this->sql;
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\pdo;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_create;
 | 
			
		||||
use nulib\db\_private\Tcreate;
 | 
			
		||||
 | 
			
		||||
class _query_create extends _query_base {
 | 
			
		||||
  use Tcreate;
 | 
			
		||||
  const SCHEMA = _create::SCHEMA;
 | 
			
		||||
}
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\pdo;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_delete;
 | 
			
		||||
use nulib\db\_private\Tdelete;
 | 
			
		||||
 | 
			
		||||
class _query_delete extends _query_base {
 | 
			
		||||
  use Tdelete;
 | 
			
		||||
  const SCHEMA = _delete::SCHEMA;
 | 
			
		||||
}
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\pdo;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_generic;
 | 
			
		||||
use nulib\db\_private\Tgeneric;
 | 
			
		||||
 | 
			
		||||
class _query_generic extends _query_base {
 | 
			
		||||
  use Tgeneric;
 | 
			
		||||
  const SCHEMA = _generic::SCHEMA;
 | 
			
		||||
}
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\pdo;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_insert;
 | 
			
		||||
use nulib\db\_private\Tinsert;
 | 
			
		||||
 | 
			
		||||
class _query_insert extends _query_base {
 | 
			
		||||
  use Tinsert;
 | 
			
		||||
  const SCHEMA = _insert::SCHEMA;
 | 
			
		||||
}
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\pdo;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_select;
 | 
			
		||||
use nulib\db\_private\Tselect;
 | 
			
		||||
 | 
			
		||||
class _query_select extends _query_base {
 | 
			
		||||
  use Tselect;
 | 
			
		||||
  const SCHEMA = _select::SCHEMA;
 | 
			
		||||
}
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\pdo;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_update;
 | 
			
		||||
use nulib\db\_private\Tupdate;
 | 
			
		||||
 | 
			
		||||
class _query_update extends _query_base {
 | 
			
		||||
  use Tupdate;
 | 
			
		||||
  const SCHEMA = _update::SCHEMA;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										294
									
								
								php/src/db/pgsql/Pgsql.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										294
									
								
								php/src/db/pgsql/Pgsql.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,294 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\pgsql;
 | 
			
		||||
 | 
			
		||||
use nulib\cl;
 | 
			
		||||
use nulib\db\_private\_config;
 | 
			
		||||
use nulib\db\_private\Tvalues;
 | 
			
		||||
use nulib\db\IDatabase;
 | 
			
		||||
use nulib\db\ITransactor;
 | 
			
		||||
use nulib\php\func;
 | 
			
		||||
use nulib\ValueException;
 | 
			
		||||
 | 
			
		||||
class Pgsql implements IDatabase {
 | 
			
		||||
  use Tvalues;
 | 
			
		||||
 | 
			
		||||
  static function with($pgsql, ?array $params=null): self {
 | 
			
		||||
    if ($pgsql instanceof static) {
 | 
			
		||||
      return $pgsql;
 | 
			
		||||
    } elseif ($pgsql instanceof self) {
 | 
			
		||||
      # recréer avec les mêmes paramètres
 | 
			
		||||
      return new static(null, cl::merge([
 | 
			
		||||
        "dbconn" => $pgsql->dbconn,
 | 
			
		||||
        "options" => $pgsql->options,
 | 
			
		||||
        "config" => $pgsql->config,
 | 
			
		||||
        "migration" => $pgsql->migration,
 | 
			
		||||
      ], $params));
 | 
			
		||||
    } else {
 | 
			
		||||
      return new static($pgsql, $params);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  protected const OPTIONS = [
 | 
			
		||||
    "persistent" => true,
 | 
			
		||||
    "force_new" => false,
 | 
			
		||||
    "serial_support" => true,
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  const CONFIG = null;
 | 
			
		||||
 | 
			
		||||
  const MIGRATION = null;
 | 
			
		||||
 | 
			
		||||
  const params_SCHEMA = [
 | 
			
		||||
    "dbconn" => ["array"],
 | 
			
		||||
    "options" => ["?array|callable"],
 | 
			
		||||
    "replace_config" => ["?array|callable"],
 | 
			
		||||
    "config" => ["?array|callable"],
 | 
			
		||||
    "migration" => ["?array|string|callable"],
 | 
			
		||||
    "auto_open" => ["bool", true],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  const dbconn_SCHEMA = [
 | 
			
		||||
    "" => "?string",
 | 
			
		||||
    "host" => "string",
 | 
			
		||||
    "hostaddr" => "?string",
 | 
			
		||||
    "port" => "?int",
 | 
			
		||||
    "dbname" => "string",
 | 
			
		||||
    "user" => "string",
 | 
			
		||||
    "password" => "string",
 | 
			
		||||
    "connect_timeout" => "?int",
 | 
			
		||||
    "options" => "?string",
 | 
			
		||||
    "sslmode" => "?string",
 | 
			
		||||
    "service" => "?string",
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  protected const dbconn_MAP = [
 | 
			
		||||
    "name" => "dbname",
 | 
			
		||||
    "pass" => "password",
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  const options_SCHEMA = [
 | 
			
		||||
    "persistent" => ["bool", self::OPTIONS["persistent"]],
 | 
			
		||||
    "force_new" => ["bool", self::OPTIONS["force_new"]],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  function __construct($dbconn=null, ?array $params=null) {
 | 
			
		||||
    if ($dbconn !== null) {
 | 
			
		||||
      if (!is_array($dbconn)) {
 | 
			
		||||
        $dbconn = ["" => $dbconn];
 | 
			
		||||
        #XXX à terme, il faudra interroger config
 | 
			
		||||
        #$tmp = config::db($dbconn);
 | 
			
		||||
        #if ($tmp !== null) $dbconn = $tmp;
 | 
			
		||||
        #else $dbconn = ["" => $dbconn];
 | 
			
		||||
      }
 | 
			
		||||
      $params["dbconn"] = $dbconn;
 | 
			
		||||
    }
 | 
			
		||||
    # dbconn
 | 
			
		||||
    $this->dbconn = $params["dbconn"] ?? null;
 | 
			
		||||
    # options
 | 
			
		||||
    $this->options = $params["options"] ?? static::OPTIONS;
 | 
			
		||||
    # configuration
 | 
			
		||||
    $config = $params["replace_config"] ?? null;
 | 
			
		||||
    if ($config === null) {
 | 
			
		||||
      $config = $params["config"] ?? static::CONFIG;
 | 
			
		||||
      if (is_callable($config)) $config = [$config];
 | 
			
		||||
    }
 | 
			
		||||
    $this->config = $config;
 | 
			
		||||
    # migrations
 | 
			
		||||
    $this->migration = $params["migration"] ?? static::MIGRATION;
 | 
			
		||||
    #
 | 
			
		||||
    $defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
 | 
			
		||||
    if ($params["auto_open"] ?? $defaultAutoOpen) {
 | 
			
		||||
      $this->open();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected ?array $dbconn;
 | 
			
		||||
 | 
			
		||||
  /** @var array|callable|null */
 | 
			
		||||
  protected $options;
 | 
			
		||||
 | 
			
		||||
  /** @var array|string|callable */
 | 
			
		||||
  protected $config;
 | 
			
		||||
 | 
			
		||||
  /** @var array|string|callable */
 | 
			
		||||
  protected $migration;
 | 
			
		||||
 | 
			
		||||
  /** @var resource */
 | 
			
		||||
  protected $db = null;
 | 
			
		||||
 | 
			
		||||
  function open(): self {
 | 
			
		||||
    if ($this->db === null) {
 | 
			
		||||
      $dbconn = $this->dbconn;
 | 
			
		||||
      $connection_string = [$dbconn[""] ?? null];
 | 
			
		||||
      unset($dbconn[""]);
 | 
			
		||||
      foreach ($dbconn as $key => $value) {
 | 
			
		||||
        if ($value === null) continue;
 | 
			
		||||
        $value = strval($value);
 | 
			
		||||
        if ($value === "" || preg_match("/[ '\\\\]/", $value)) {
 | 
			
		||||
          $value = str_replace("\\", "\\\\", $value);
 | 
			
		||||
          $value = str_replace("'", "\\'", $value);
 | 
			
		||||
          $value = "'$value'";
 | 
			
		||||
        }
 | 
			
		||||
        $key = cl::get(self::dbconn_MAP, $key, $key);
 | 
			
		||||
        $connection_string[] = "$key=$value";
 | 
			
		||||
      }
 | 
			
		||||
      $connection_string = implode(" ", array_filter($connection_string));
 | 
			
		||||
      $options = $this->options;
 | 
			
		||||
      if (is_callable($options)) {
 | 
			
		||||
        $options = func::with($options)->bind($this, true)->invoke();
 | 
			
		||||
      }
 | 
			
		||||
      $forceNew = $options["force_new"] ?? false;
 | 
			
		||||
      $flags = $forceNew? PGSQL_CONNECT_FORCE_NEW: 0;
 | 
			
		||||
 | 
			
		||||
      if ($options["persistent"] ?? true) $db = pg_pconnect($connection_string, $flags);
 | 
			
		||||
      else $db = pg_connect($connection_string, $flags);
 | 
			
		||||
      if ($db === false) throw new PgsqlException("unable to connect");
 | 
			
		||||
      $this->db = $db;
 | 
			
		||||
 | 
			
		||||
      _config::with($this->config)->configure($this);
 | 
			
		||||
      //_migration::with($this->migration)->migrate($this);
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function close(): self {
 | 
			
		||||
    if ($this->db !== null) {
 | 
			
		||||
      pg_close($this->db);
 | 
			
		||||
      $this->db = null;
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function db() {
 | 
			
		||||
    $this->open();
 | 
			
		||||
    return $this->db;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function _exec(string $query): bool {
 | 
			
		||||
    $result = pg_query($this->db(), $query);
 | 
			
		||||
    if ($result === false) return false;
 | 
			
		||||
    pg_free_result($result);
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getLastSerial() {
 | 
			
		||||
    $db = $this->db();
 | 
			
		||||
    $result = @pg_query($db, "select lastval()");
 | 
			
		||||
    if ($result === false) return false;
 | 
			
		||||
    $lastSerial = pg_fetch_row($result)[0];
 | 
			
		||||
    pg_free_result($result);
 | 
			
		||||
    return $lastSerial;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function exec($query, ?array $params=null) {
 | 
			
		||||
    $db = $this->db();
 | 
			
		||||
    $query = new _pgsqlQuery($query, $params);
 | 
			
		||||
    $result = $query->_exec($db);
 | 
			
		||||
    $serialSupport = $this->options["serial_support"] ?? true;
 | 
			
		||||
    if ($serialSupport && $query->isInsert()) return $this->getLastSerial();
 | 
			
		||||
    $affected_rows = pg_affected_rows($result);
 | 
			
		||||
    pg_free_result($result);
 | 
			
		||||
    return $affected_rows;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @var ITransactor[] */
 | 
			
		||||
  protected ?array $transactors = null;
 | 
			
		||||
 | 
			
		||||
  function willUpdate(...$transactors): self {
 | 
			
		||||
    foreach ($transactors as $transactor) {
 | 
			
		||||
      if ($transactor instanceof ITransactor) {
 | 
			
		||||
        $this->transactors[] = $transactor;
 | 
			
		||||
        $transactor->willUpdate();
 | 
			
		||||
      } else {
 | 
			
		||||
        throw ValueException::invalid_type($transactor, ITransactor::class);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function inTransaction(?bool &$inerror=null): bool {
 | 
			
		||||
    $status = pg_transaction_status($this->db());
 | 
			
		||||
    if ($status === PGSQL_TRANSACTION_ACTIVE || $status === PGSQL_TRANSACTION_INTRANS) {
 | 
			
		||||
      $inerror = false;
 | 
			
		||||
      return true;
 | 
			
		||||
    } elseif ($status === PGSQL_TRANSACTION_INERROR) {
 | 
			
		||||
      $inerror = true;
 | 
			
		||||
      return true;
 | 
			
		||||
    } else {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function beginTransaction(?callable $func=null, bool $commit=true): void {
 | 
			
		||||
    $this->_exec("begin");
 | 
			
		||||
    if ($this->transactors !== null) {
 | 
			
		||||
      foreach ($this->transactors as $transactor) {
 | 
			
		||||
        $transactor->beginTransaction();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if ($func !== null) {
 | 
			
		||||
      $commited = false;
 | 
			
		||||
      try {
 | 
			
		||||
        func::call($func, $this);
 | 
			
		||||
        if ($commit) {
 | 
			
		||||
          $this->commit();
 | 
			
		||||
          $commited = true;
 | 
			
		||||
        }
 | 
			
		||||
      } finally {
 | 
			
		||||
        if ($commit && !$commited) $this->rollback();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function commit(): void {
 | 
			
		||||
    $this->_exec("commit");
 | 
			
		||||
    if ($this->transactors !== null) {
 | 
			
		||||
      foreach ($this->transactors as $transactor) {
 | 
			
		||||
        $transactor->commit();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function rollback(): void {
 | 
			
		||||
    $this->_exec("rollback");
 | 
			
		||||
    if ($this->transactors !== null) {
 | 
			
		||||
      foreach ($this->transactors as $transactor) {
 | 
			
		||||
        $transactor->rollback();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function get($query, ?array $params=null, bool $entireRow=false) {
 | 
			
		||||
    $db = $this->db();
 | 
			
		||||
    $query = new _pgsqlQuery($query, $params);
 | 
			
		||||
    $result = $query->_exec($db);
 | 
			
		||||
    $row = pg_fetch_assoc($result);
 | 
			
		||||
    pg_free_result($result);
 | 
			
		||||
    if ($row === false) return null;
 | 
			
		||||
    $this->verifixRow($row);
 | 
			
		||||
    if ($entireRow) return $row;
 | 
			
		||||
    else return cl::first($row);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function one($query, ?array $params=null): ?array {
 | 
			
		||||
    return $this->get($query, $params, true);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function all($query, ?array $params=null, $primaryKeys=null): iterable {
 | 
			
		||||
    $db = $this->db();
 | 
			
		||||
    $query = new _pgsqlQuery($query, $params);
 | 
			
		||||
    $result = $query->_exec($db);
 | 
			
		||||
    $primaryKeys = cl::withn($primaryKeys);
 | 
			
		||||
    while (($row = pg_fetch_assoc($result)) !== false) {
 | 
			
		||||
      $this->verifixRow($row);
 | 
			
		||||
      if ($primaryKeys !== null) {
 | 
			
		||||
        $key = implode("-", cl::select($row, $primaryKeys));
 | 
			
		||||
        yield $key => $row;
 | 
			
		||||
      } else {
 | 
			
		||||
        yield $row;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    pg_free_result($result);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								php/src/db/pgsql/PgsqlException.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								php/src/db/pgsql/PgsqlException.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\pgsql;
 | 
			
		||||
 | 
			
		||||
use Exception;
 | 
			
		||||
use RuntimeException;
 | 
			
		||||
use SQLite3;
 | 
			
		||||
 | 
			
		||||
class PgsqlException extends RuntimeException {
 | 
			
		||||
  static final function last_error($db): self {
 | 
			
		||||
    return new static(pg_last_error($db));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static final function wrap(Exception $e): self {
 | 
			
		||||
    return new static($e->getMessage(), $e->getCode(), $e);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								php/src/db/pgsql/PgsqlStorage.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								php/src/db/pgsql/PgsqlStorage.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\pgsql;
 | 
			
		||||
 | 
			
		||||
use nulib\cl;
 | 
			
		||||
use nulib\db\CapacitorChannel;
 | 
			
		||||
use nulib\db\CapacitorStorage;
 | 
			
		||||
 | 
			
		||||
class PgsqlStorage extends CapacitorStorage {
 | 
			
		||||
  const SERDATA_DEFINITION = "text";
 | 
			
		||||
  const SERSUM_DEFINITION = "varchar(40)";
 | 
			
		||||
  const SERTS_DEFINITION = "timestamp";
 | 
			
		||||
 | 
			
		||||
  function __construct($pgsql) {
 | 
			
		||||
    $this->db = Pgsql::with($pgsql);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected Pgsql $db;
 | 
			
		||||
 | 
			
		||||
  function db(): Pgsql {
 | 
			
		||||
    return $this->db;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const PRIMARY_KEY_DEFINITION = [
 | 
			
		||||
    "id_" => "serial primary key",
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  function _getMigration(CapacitorChannel $channel): _pgsqlMigration {
 | 
			
		||||
    return new _pgsqlMigration(cl::merge([
 | 
			
		||||
      $this->_createSql($channel),
 | 
			
		||||
    ], $channel->getMigration()), $channel->getName());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function _getCreateSql(CapacitorChannel $channel): string {
 | 
			
		||||
    $query = new _pgsqlQuery($this->_createSql($channel));
 | 
			
		||||
    return self::format_sql($channel, $query->getSql());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function _addToChannelsSql(CapacitorChannel $channel): array {
 | 
			
		||||
    return cl::merge(parent::_addToChannelsSql($channel), [
 | 
			
		||||
      "suffix" => "on conflict (name) do nothing",
 | 
			
		||||
    ]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function _exists(CapacitorChannel $channel): bool {
 | 
			
		||||
    $tableName = $channel->getTableName();
 | 
			
		||||
    if (($index = strpos($tableName, ".")) !== false) {
 | 
			
		||||
      $schemaName = substr($tableName, 0, $index);
 | 
			
		||||
      $tableName = substr($tableName, $index + 1);
 | 
			
		||||
    } else {
 | 
			
		||||
      $schemaName = "public";
 | 
			
		||||
    }
 | 
			
		||||
    return null !== $this->db->get([
 | 
			
		||||
      "select tablename from pg_tables",
 | 
			
		||||
      "where" => [
 | 
			
		||||
        "schemaname" => $schemaName,
 | 
			
		||||
        "tablename" => $tableName,
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function close(): void {
 | 
			
		||||
    $this->db->close();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								php/src/db/pgsql/_pgsqlMigration.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								php/src/db/pgsql/_pgsqlMigration.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\pgsql;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_migration;
 | 
			
		||||
 | 
			
		||||
class _pgsqlMigration extends _migration {
 | 
			
		||||
  static function with($migration): self {
 | 
			
		||||
    if ($migration instanceof self) return $migration;
 | 
			
		||||
    else return new static($migration);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function setMigrated(string $name, bool $done): void {
 | 
			
		||||
    $this->db->exec([
 | 
			
		||||
      "insert",
 | 
			
		||||
      "into" => static::MIGRATION_TABLE,
 | 
			
		||||
      "values" => [
 | 
			
		||||
        "channel" => $this->channel,
 | 
			
		||||
        "name" => $name,
 | 
			
		||||
        "done" => $done? 1: 0,
 | 
			
		||||
      ],
 | 
			
		||||
      "suffix" => "on conflict (channel, name) do update set done = :done",
 | 
			
		||||
    ]);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								php/src/db/pgsql/_pgsqlQuery.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								php/src/db/pgsql/_pgsqlQuery.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,44 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\pgsql;
 | 
			
		||||
 | 
			
		||||
use nulib\cv;
 | 
			
		||||
use nulib\db\_private\_base;
 | 
			
		||||
use nulib\db\_private\Tbindings;
 | 
			
		||||
use nulib\output\msg;
 | 
			
		||||
 | 
			
		||||
class _pgsqlQuery extends _base {
 | 
			
		||||
  use Tbindings;
 | 
			
		||||
 | 
			
		||||
  const DEBUG_QUERIES = false;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return resource
 | 
			
		||||
   */
 | 
			
		||||
  function _exec($db) {
 | 
			
		||||
    $sql = $this->sql;
 | 
			
		||||
    $bindings = $this->bindings;
 | 
			
		||||
    if (static::DEBUG_QUERIES) {#XXX
 | 
			
		||||
      msg::info($sql);
 | 
			
		||||
      //msg::info(var_export($bindings, true));
 | 
			
		||||
    }
 | 
			
		||||
    if ($bindings !== null) {
 | 
			
		||||
      # trier d'abord les champ par ordre de longueur, pour éviter les overlaps
 | 
			
		||||
      $names = array_keys($bindings);
 | 
			
		||||
      usort($names, function ($a, $b) {
 | 
			
		||||
        return -cv::compare(strlen(strval($a)), strlen(strval($b)));
 | 
			
		||||
      });
 | 
			
		||||
      $bparams = [];
 | 
			
		||||
      $number = 1;
 | 
			
		||||
      foreach ($names as $name) {
 | 
			
		||||
        $sql = str_replace(":$name", "\$$number", $sql);
 | 
			
		||||
        $bparams[] = $bindings[$name];
 | 
			
		||||
        $number++;
 | 
			
		||||
      }
 | 
			
		||||
      $result = pg_query_params($db, $sql, $bparams);
 | 
			
		||||
    } else {
 | 
			
		||||
      $result = pg_query($db, $sql);
 | 
			
		||||
    }
 | 
			
		||||
    if ($result === false) throw PgsqlException::last_error($db);
 | 
			
		||||
    return $result;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -3,10 +3,11 @@ namespace nulib\db\sqlite;
 | 
			
		||||
 | 
			
		||||
use Generator;
 | 
			
		||||
use nulib\cl;
 | 
			
		||||
use nulib\db\_private\_config;
 | 
			
		||||
use nulib\db\_private\Tvalues;
 | 
			
		||||
use nulib\db\IDatabase;
 | 
			
		||||
use nulib\db\ITransactor;
 | 
			
		||||
use nulib\php\nur_func;
 | 
			
		||||
use nulib\php\func;
 | 
			
		||||
use nulib\ValueException;
 | 
			
		||||
use SQLite3;
 | 
			
		||||
use SQLite3Result;
 | 
			
		||||
@ -29,7 +30,7 @@ class Sqlite implements IDatabase {
 | 
			
		||||
        "encryption_key" => $sqlite->encryptionKey,
 | 
			
		||||
        "allow_wal" => $sqlite->allowWal,
 | 
			
		||||
        "config" => $sqlite->config,
 | 
			
		||||
        "migrate" => $sqlite->migration,
 | 
			
		||||
        "migration" => $sqlite->migration,
 | 
			
		||||
      ], $params));
 | 
			
		||||
    } elseif (is_array($sqlite)) {
 | 
			
		||||
      return new static(null, cl::merge($sqlite, $params));
 | 
			
		||||
@ -71,7 +72,7 @@ class Sqlite implements IDatabase {
 | 
			
		||||
 | 
			
		||||
  const CONFIG = null;
 | 
			
		||||
 | 
			
		||||
  const MIGRATE = null;
 | 
			
		||||
  const MIGRATION = null;
 | 
			
		||||
 | 
			
		||||
  const params_SCHEMA = [
 | 
			
		||||
    "file" => ["string", ""],
 | 
			
		||||
@ -80,7 +81,7 @@ class Sqlite implements IDatabase {
 | 
			
		||||
    "allow_wal" => ["?bool"],
 | 
			
		||||
    "replace_config" => ["?array|callable"],
 | 
			
		||||
    "config" => ["?array|callable"],
 | 
			
		||||
    "migrate" => ["?array|string|callable"],
 | 
			
		||||
    "migration" => ["?array|string|callable"],
 | 
			
		||||
    "auto_open" => ["bool", true],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
@ -108,7 +109,7 @@ class Sqlite implements IDatabase {
 | 
			
		||||
    }
 | 
			
		||||
    $this->config = $config;
 | 
			
		||||
    # migrations
 | 
			
		||||
    $this->migration = $params["migrate"] ?? static::MIGRATE;
 | 
			
		||||
    $this->migration = $params["migration"] ?? static::MIGRATION;
 | 
			
		||||
    #
 | 
			
		||||
    $defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
 | 
			
		||||
    $this->inTransaction = false;
 | 
			
		||||
@ -149,7 +150,7 @@ class Sqlite implements IDatabase {
 | 
			
		||||
    if ($this->db === null) {
 | 
			
		||||
      $this->db = new SQLite3($this->file, $this->flags, $this->encryptionKey);
 | 
			
		||||
      _config::with($this->config)->configure($this);
 | 
			
		||||
      _migration::with($this->migration)->migrate($this);
 | 
			
		||||
      _sqliteMigration::with($this->migration)->migrate($this);
 | 
			
		||||
      $this->inTransaction = false;
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
@ -180,15 +181,10 @@ class Sqlite implements IDatabase {
 | 
			
		||||
    return $this->db()->exec($query);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static function is_insert(?string $sql): bool {
 | 
			
		||||
    if ($sql === null) return false;
 | 
			
		||||
    return preg_match('/^\s*insert\b/i', $sql);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function exec($query, ?array $params=null) {
 | 
			
		||||
    $db = $this->db();
 | 
			
		||||
    $query = new _query_base($query, $params);
 | 
			
		||||
    if ($query->useStmt($db, $stmt, $sql)) {
 | 
			
		||||
    $query = new _sqliteQuery($query, $params);
 | 
			
		||||
    if ($query->_use_stmt($db, $stmt, $sql)) {
 | 
			
		||||
      try {
 | 
			
		||||
        $result = $stmt->execute();
 | 
			
		||||
        if ($result === false) return false;
 | 
			
		||||
@ -201,7 +197,7 @@ class Sqlite implements IDatabase {
 | 
			
		||||
    } else {
 | 
			
		||||
      $result = $db->exec($sql);
 | 
			
		||||
      if ($result === false) return false;
 | 
			
		||||
      if (self::is_insert($sql)) return $db->lastInsertRowID();
 | 
			
		||||
      if ($query->isInsert()) return $db->lastInsertRowID();
 | 
			
		||||
      else return $db->changes();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -237,7 +233,7 @@ class Sqlite implements IDatabase {
 | 
			
		||||
    if ($func !== null) {
 | 
			
		||||
      $commited = false;
 | 
			
		||||
      try {
 | 
			
		||||
        nur_func::call($func, $this);
 | 
			
		||||
        func::call($func, $this);
 | 
			
		||||
        if ($commit) {
 | 
			
		||||
          $this->commit();
 | 
			
		||||
          $commited = true;
 | 
			
		||||
@ -274,8 +270,8 @@ class Sqlite implements IDatabase {
 | 
			
		||||
 | 
			
		||||
  function get($query, ?array $params=null, bool $entireRow=false) {
 | 
			
		||||
    $db = $this->db();
 | 
			
		||||
    $query = new _query_base($query, $params);
 | 
			
		||||
    if ($query->useStmt($db, $stmt, $sql)) {
 | 
			
		||||
    $query = new _sqliteQuery($query, $params);
 | 
			
		||||
    if ($query->_use_stmt($db, $stmt, $sql)) {
 | 
			
		||||
      try {
 | 
			
		||||
        $result = $this->checkResult($stmt->execute());
 | 
			
		||||
        try {
 | 
			
		||||
@ -300,7 +296,7 @@ class Sqlite implements IDatabase {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function _fetchResult(SQLite3Result $result, ?SQLite3Stmt $stmt=null, $primaryKeys=null): Generator {
 | 
			
		||||
    if ($primaryKeys !== null) $primaryKeys = cl::with($primaryKeys);
 | 
			
		||||
    $primaryKeys = cl::withn($primaryKeys);
 | 
			
		||||
    try {
 | 
			
		||||
      while (($row = $result->fetchArray(SQLITE3_ASSOC)) !== false) {
 | 
			
		||||
        $this->verifixRow($row);
 | 
			
		||||
@ -317,14 +313,10 @@ class Sqlite implements IDatabase {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * si $primaryKeys est fourni, le résultat est indexé sur la(es) colonne(s)
 | 
			
		||||
   * spécifiée(s)
 | 
			
		||||
   */
 | 
			
		||||
  function all($query, ?array $params=null, $primaryKeys=null): iterable {
 | 
			
		||||
    $db = $this->db();
 | 
			
		||||
    $query = new _query_base($query, $params);
 | 
			
		||||
    if ($query->useStmt($db, $stmt, $sql)) {
 | 
			
		||||
    $query = new _sqliteQuery($query, $params);
 | 
			
		||||
    if ($query->_use_stmt($db, $stmt, $sql)) {
 | 
			
		||||
      $result = $this->checkResult($stmt->execute());
 | 
			
		||||
      return $this->_fetchResult($result, $stmt, $primaryKeys);
 | 
			
		||||
    } else {
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\sqlite;
 | 
			
		||||
 | 
			
		||||
use nulib\cl;
 | 
			
		||||
use nulib\db\CapacitorChannel;
 | 
			
		||||
use nulib\db\CapacitorStorage;
 | 
			
		||||
 | 
			
		||||
@ -12,8 +13,7 @@ class SqliteStorage extends CapacitorStorage {
 | 
			
		||||
    $this->db = Sqlite::with($sqlite);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @var Sqlite */
 | 
			
		||||
  protected $db;
 | 
			
		||||
  protected Sqlite $db;
 | 
			
		||||
 | 
			
		||||
  function db(): Sqlite {
 | 
			
		||||
    return $this->db;
 | 
			
		||||
@ -23,8 +23,14 @@ class SqliteStorage extends CapacitorStorage {
 | 
			
		||||
    "id_" => "integer primary key autoincrement",
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  function _getMigration(CapacitorChannel $channel): _sqliteMigration {
 | 
			
		||||
    return new _sqliteMigration(cl::merge([
 | 
			
		||||
      $this->_createSql($channel),
 | 
			
		||||
    ], $channel->getMigration()), $channel->getName());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function _getCreateSql(CapacitorChannel $channel): string {
 | 
			
		||||
    $query = new _query_base($this->_createSql($channel));
 | 
			
		||||
    $query = new _sqliteQuery($this->_createSql($channel));
 | 
			
		||||
    return self::format_sql($channel, $query->getSql());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -39,54 +45,33 @@ class SqliteStorage extends CapacitorStorage {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function channelExists(string $name): bool {
 | 
			
		||||
    $name = $this->db->get([
 | 
			
		||||
      "select name from _channels",
 | 
			
		||||
    return null !== $this->db->get([
 | 
			
		||||
      "select name",
 | 
			
		||||
      "from" => static::CHANNELS_TABLE,
 | 
			
		||||
      "where" => ["name" => $name],
 | 
			
		||||
    ]);
 | 
			
		||||
    return $name !== null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function _addToChannelsSql(CapacitorChannel $channel): array {
 | 
			
		||||
    $sql = parent::_addToChannelsSql($channel);
 | 
			
		||||
    $sql[0] = "insert or ignore";
 | 
			
		||||
    return $sql;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function _afterCreate(CapacitorChannel $channel): void {
 | 
			
		||||
    $db = $this->db;
 | 
			
		||||
    if (!$this->tableExists("_channels")) {
 | 
			
		||||
    if (!$this->tableExists(static::CHANNELS_TABLE)) {
 | 
			
		||||
      # ne pas créer si la table existe déjà, pour éviter d'avoir besoin d'un
 | 
			
		||||
      # verrou en écriture
 | 
			
		||||
      $db->exec([
 | 
			
		||||
        "create table if not exists",
 | 
			
		||||
        "table" => "_channels",
 | 
			
		||||
        "cols" => [
 | 
			
		||||
          "name" => "varchar primary key",
 | 
			
		||||
          "table_name" => "varchar",
 | 
			
		||||
          "class" => "varchar",
 | 
			
		||||
        ],
 | 
			
		||||
      ]);
 | 
			
		||||
      $db->exec($this->_createChannelsSql());
 | 
			
		||||
    }
 | 
			
		||||
    if (!$this->channelExists($channel->getName())) {
 | 
			
		||||
      # ne pas insérer si la ligne existe déjà, pour éviter d'avoir besoin d'un
 | 
			
		||||
      # verrou en écriture
 | 
			
		||||
      $db->exec([
 | 
			
		||||
        "insert",
 | 
			
		||||
        "into" => "_channels",
 | 
			
		||||
        "values" => [
 | 
			
		||||
          "name" => $channel->getName(),
 | 
			
		||||
          "table_name" => $channel->getTableName(),
 | 
			
		||||
          "class" => get_class($channel),
 | 
			
		||||
        ],
 | 
			
		||||
        "suffix" => "on conflict do nothing",
 | 
			
		||||
      ]);
 | 
			
		||||
      $db->exec($this->_addToChannelsSql($channel));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function _beforeReset(CapacitorChannel $channel): void {
 | 
			
		||||
    $this->db->exec([
 | 
			
		||||
      "delete",
 | 
			
		||||
      "from" => "_channels",
 | 
			
		||||
      "where" => [
 | 
			
		||||
        "name" => $channel->getName(),
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function _exists(CapacitorChannel $channel): bool {
 | 
			
		||||
    return $this->tableExists($channel->getTableName());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -1,36 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\sqlite;
 | 
			
		||||
 | 
			
		||||
use nulib\php\nur_func;
 | 
			
		||||
 | 
			
		||||
class _config {
 | 
			
		||||
  static function with($configs): self {
 | 
			
		||||
    if ($configs instanceof static) return $configs;
 | 
			
		||||
    return new static($configs);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const CONFIG = null;
 | 
			
		||||
 | 
			
		||||
  function __construct($configs) {
 | 
			
		||||
    if ($configs === null) $configs = static::CONFIG;
 | 
			
		||||
    if ($configs === null) $configs = [];
 | 
			
		||||
    elseif (is_string($configs)) $configs = [$configs];
 | 
			
		||||
    elseif (is_callable($configs)) $configs = [$configs];
 | 
			
		||||
    elseif (!is_array($configs)) $configs = [strval($configs)];
 | 
			
		||||
    $this->configs = $configs;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @var array */
 | 
			
		||||
  protected $configs;
 | 
			
		||||
 | 
			
		||||
  function configure(Sqlite $sqlite): void {
 | 
			
		||||
    foreach ($this->configs as $key => $config) {
 | 
			
		||||
      if (is_string($config) && !nur_func::is_method($config)) {
 | 
			
		||||
        $sqlite->exec($config);
 | 
			
		||||
      } else {
 | 
			
		||||
        nur_func::ensure_func($config, $this, $args);
 | 
			
		||||
        nur_func::call($config, $sqlite, $key, ...$args);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,55 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\sqlite;
 | 
			
		||||
 | 
			
		||||
use nulib\php\nur_func;
 | 
			
		||||
 | 
			
		||||
class _migration {
 | 
			
		||||
  static function with($migrations): self {
 | 
			
		||||
    if ($migrations instanceof static) {
 | 
			
		||||
      return $migrations;
 | 
			
		||||
    } elseif ($migrations instanceof self) {
 | 
			
		||||
      return new static($migrations->migrations);
 | 
			
		||||
    } else {
 | 
			
		||||
      return new static($migrations);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const MIGRATE = null;
 | 
			
		||||
 | 
			
		||||
  function __construct($migrations) {
 | 
			
		||||
    if ($migrations === null) $migrations = static::MIGRATE;
 | 
			
		||||
    if ($migrations === null) $migrations = [];
 | 
			
		||||
    elseif (is_string($migrations)) $migrations = [$migrations];
 | 
			
		||||
    elseif (is_callable($migrations)) $migrations = [$migrations];
 | 
			
		||||
    elseif (!is_array($migrations)) $migrations = [strval($migrations)];
 | 
			
		||||
    $this->migrations = $migrations;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @var callable[]|string[]  */
 | 
			
		||||
  protected $migrations;
 | 
			
		||||
  
 | 
			
		||||
  function migrate(Sqlite $sqlite): void {
 | 
			
		||||
    $sqlite->exec("create table if not exists _migration(key varchar primary key, value varchar not null, done integer default 0)");
 | 
			
		||||
    foreach ($this->migrations as $key => $migration) {
 | 
			
		||||
      $exists = $sqlite->get("select 1 from _migration where key = :key and done = 1", [
 | 
			
		||||
        "key" => $key,
 | 
			
		||||
      ]);
 | 
			
		||||
      if (!$exists) {
 | 
			
		||||
        $sqlite->exec("insert or replace into _migration(key, value, done) values(:key, :value, :done)", [
 | 
			
		||||
          "key" => $key,
 | 
			
		||||
          "value" => $migration,
 | 
			
		||||
          "done" => 0,
 | 
			
		||||
        ]);
 | 
			
		||||
        if (is_string($migration) && !nur_func::is_method($migration)) {
 | 
			
		||||
          $sqlite->exec($migration);
 | 
			
		||||
        } else {
 | 
			
		||||
          nur_func::ensure_func($migration, $this, $args);
 | 
			
		||||
          nur_func::call($migration, $sqlite, $key, ...$args);
 | 
			
		||||
        }
 | 
			
		||||
        $sqlite->exec("update _migration set done = 1 where key = :key", [
 | 
			
		||||
          "key" => $key,
 | 
			
		||||
        ]);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,62 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\sqlite;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_base;
 | 
			
		||||
use nulib\db\_private\Tbindings;
 | 
			
		||||
use nulib\output\msg;
 | 
			
		||||
use nulib\ValueException;
 | 
			
		||||
use SQLite3;
 | 
			
		||||
use SQLite3Stmt;
 | 
			
		||||
 | 
			
		||||
class _query_base extends _base {
 | 
			
		||||
  use Tbindings;
 | 
			
		||||
 | 
			
		||||
  protected static function verifix(&$sql, ?array &$bindinds=null, ?array &$meta=null): void {
 | 
			
		||||
    if (is_array($sql)) {
 | 
			
		||||
      $prefix = $sql[0] ?? null;
 | 
			
		||||
      if ($prefix === null) {
 | 
			
		||||
        throw new ValueException("requête invalide");
 | 
			
		||||
      } elseif (_query_create::isa($prefix)) {
 | 
			
		||||
        $sql = _query_create::parse($sql, $bindinds);
 | 
			
		||||
      } elseif (_query_select::isa($prefix)) {
 | 
			
		||||
        $sql = _query_select::parse($sql, $bindinds);
 | 
			
		||||
      } elseif (_query_insert::isa($prefix)) {
 | 
			
		||||
        $sql = _query_insert::parse($sql, $bindinds);
 | 
			
		||||
      } elseif (_query_update::isa($prefix)) {
 | 
			
		||||
        $sql = _query_update::parse($sql, $bindinds);
 | 
			
		||||
      } elseif (_query_delete::isa($prefix)) {
 | 
			
		||||
        $sql = _query_delete::parse($sql, $bindinds);
 | 
			
		||||
      } elseif (_query_generic::isa($prefix)) {
 | 
			
		||||
        $sql = _query_generic::parse($sql, $bindinds);
 | 
			
		||||
      } else {
 | 
			
		||||
        throw SqliteException::wrap(ValueException::invalid_kind($sql, "query"));
 | 
			
		||||
      }
 | 
			
		||||
    } elseif (!is_string($sql)) {
 | 
			
		||||
      $sql = strval($sql);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const DEBUG_QUERIES = false;
 | 
			
		||||
 | 
			
		||||
  function useStmt(SQLite3 $db, ?SQLite3Stmt &$stmt=null, ?string &$sql=null): bool {
 | 
			
		||||
    if (static::DEBUG_QUERIES) msg::info($this->sql); #XXX
 | 
			
		||||
    if ($this->bindings !== null) {
 | 
			
		||||
      /** @var SQLite3Stmt $stmt */
 | 
			
		||||
      $stmt = SqliteException::check($db, $db->prepare($this->sql));
 | 
			
		||||
      $close = true;
 | 
			
		||||
      try {
 | 
			
		||||
        foreach ($this->bindings as $param => $value) {
 | 
			
		||||
          $this->verifixBindings($value);
 | 
			
		||||
          SqliteException::check($db, $stmt->bindValue($param, $value));
 | 
			
		||||
        }
 | 
			
		||||
        $close = false;
 | 
			
		||||
        return true;
 | 
			
		||||
      } finally {
 | 
			
		||||
        if ($close) $stmt->close();
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      $sql = $this->sql;
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\sqlite;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_create;
 | 
			
		||||
use nulib\db\_private\Tcreate;
 | 
			
		||||
 | 
			
		||||
class _query_create extends _query_base {
 | 
			
		||||
  use Tcreate;
 | 
			
		||||
  const SCHEMA = _create::SCHEMA;
 | 
			
		||||
}
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\sqlite;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_delete;
 | 
			
		||||
use nulib\db\_private\Tdelete;
 | 
			
		||||
 | 
			
		||||
class _query_delete extends _query_base {
 | 
			
		||||
  use Tdelete;
 | 
			
		||||
  const SCHEMA = _delete::SCHEMA;
 | 
			
		||||
}
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\sqlite;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_generic;
 | 
			
		||||
use nulib\db\_private\Tgeneric;
 | 
			
		||||
 | 
			
		||||
class _query_generic extends _query_base {
 | 
			
		||||
  use Tgeneric;
 | 
			
		||||
  const SCHEMA = _generic::SCHEMA;
 | 
			
		||||
}
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\sqlite;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_insert;
 | 
			
		||||
use nulib\db\_private\Tinsert;
 | 
			
		||||
 | 
			
		||||
class _query_insert extends _query_base {
 | 
			
		||||
  use Tinsert;
 | 
			
		||||
  const SCHEMA = _insert::SCHEMA;
 | 
			
		||||
}
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\sqlite;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_select;
 | 
			
		||||
use nulib\db\_private\Tselect;
 | 
			
		||||
 | 
			
		||||
class _query_select extends _query_base {
 | 
			
		||||
  use Tselect;
 | 
			
		||||
  const SCHEMA = _select::SCHEMA;
 | 
			
		||||
}
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\sqlite;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_update;
 | 
			
		||||
use nulib\db\_private\Tupdate;
 | 
			
		||||
 | 
			
		||||
class _query_update extends _query_base {
 | 
			
		||||
  use Tupdate;
 | 
			
		||||
  const SCHEMA = _update::SCHEMA;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								php/src/db/sqlite/_sqliteMigration.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								php/src/db/sqlite/_sqliteMigration.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\sqlite;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_migration;
 | 
			
		||||
 | 
			
		||||
class _sqliteMigration extends _migration {
 | 
			
		||||
  static function with($migration): self {
 | 
			
		||||
    if ($migration instanceof self) return $migration;
 | 
			
		||||
    else return new static($migration);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function setMigrated(string $name, bool $done): void {
 | 
			
		||||
    $this->db->exec([
 | 
			
		||||
      "insert or replace",
 | 
			
		||||
      "into" => static::MIGRATION_TABLE,
 | 
			
		||||
      "values" => [
 | 
			
		||||
        "channel" => $this->channel,
 | 
			
		||||
        "name" => $name,
 | 
			
		||||
        "done" => $done? 1: 0,
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								php/src/db/sqlite/_sqliteQuery.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								php/src/db/sqlite/_sqliteQuery.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib\db\sqlite;
 | 
			
		||||
 | 
			
		||||
use nulib\db\_private\_base;
 | 
			
		||||
use nulib\db\_private\_create;
 | 
			
		||||
use nulib\db\_private\_delete;
 | 
			
		||||
use nulib\db\_private\_generic;
 | 
			
		||||
use nulib\db\_private\_insert;
 | 
			
		||||
use nulib\db\_private\_select;
 | 
			
		||||
use nulib\db\_private\_update;
 | 
			
		||||
use nulib\db\_private\Tbindings;
 | 
			
		||||
use nulib\output\msg;
 | 
			
		||||
use nulib\ValueException;
 | 
			
		||||
use SQLite3;
 | 
			
		||||
use SQLite3Stmt;
 | 
			
		||||
 | 
			
		||||
class _sqliteQuery extends _base {
 | 
			
		||||
  use Tbindings;
 | 
			
		||||
 | 
			
		||||
  const DEBUG_QUERIES = false;
 | 
			
		||||
 | 
			
		||||
  function _use_stmt(SQLite3 $db, ?SQLite3Stmt &$stmt=null, ?string &$sql=null): bool {
 | 
			
		||||
    if (static::DEBUG_QUERIES) {#XXX
 | 
			
		||||
      msg::info($this->sql);
 | 
			
		||||
      //msg::info(var_export($this->bindings, true));
 | 
			
		||||
    }
 | 
			
		||||
    if ($this->bindings !== null) {
 | 
			
		||||
      /** @var SQLite3Stmt $stmt */
 | 
			
		||||
      $stmt = SqliteException::check($db, $db->prepare($this->sql));
 | 
			
		||||
      $close = true;
 | 
			
		||||
      try {
 | 
			
		||||
        foreach ($this->bindings as $param => $value) {
 | 
			
		||||
          $this->verifixBindings($value);
 | 
			
		||||
          SqliteException::check($db, $stmt->bindValue($param, $value));
 | 
			
		||||
        }
 | 
			
		||||
        $close = false;
 | 
			
		||||
        return true;
 | 
			
		||||
      } finally {
 | 
			
		||||
        if ($close) $stmt->close();
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      $sql = $this->sql;
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -5,7 +5,7 @@ use DateTimeInterface;
 | 
			
		||||
use nulib\cl;
 | 
			
		||||
use nulib\file\TempStream;
 | 
			
		||||
use nulib\os\path;
 | 
			
		||||
use nulib\php\nur_func;
 | 
			
		||||
use nulib\php\func;
 | 
			
		||||
use nulib\php\time\DateTime;
 | 
			
		||||
use nulib\web\http;
 | 
			
		||||
 | 
			
		||||
@ -35,13 +35,8 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
 | 
			
		||||
    $this->rows = $rows;
 | 
			
		||||
    $this->index = 0;
 | 
			
		||||
    $cookFunc = $params["cook_func"] ?? null;
 | 
			
		||||
    $cookCtx = $cookArgs = null;
 | 
			
		||||
    if ($cookFunc !== null) {
 | 
			
		||||
      nur_func::ensure_func($cookFunc, $this, $cookArgs);
 | 
			
		||||
      $cookCtx = nur_func::_prepare($cookFunc);
 | 
			
		||||
    }
 | 
			
		||||
    $this->cookCtx = $cookCtx;
 | 
			
		||||
    $this->cookArgs = $cookArgs;
 | 
			
		||||
    if ($cookFunc !== null) $cookFunc = func::with($cookFunc)->bind($this, true);
 | 
			
		||||
    $this->cookFunc = $cookFunc;
 | 
			
		||||
    $this->output = $params["output"] ?? static::OUTPUT;
 | 
			
		||||
    $maxMemory = $params["max_memory"] ?? null;
 | 
			
		||||
    $throwOnError = $params["throw_on_error"] ?? null;
 | 
			
		||||
@ -60,9 +55,7 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
 | 
			
		||||
 | 
			
		||||
  protected ?string $output;
 | 
			
		||||
 | 
			
		||||
  protected ?array $cookCtx;
 | 
			
		||||
 | 
			
		||||
  protected ?array $cookArgs;
 | 
			
		||||
  protected ?func $cookFunc;
 | 
			
		||||
 | 
			
		||||
  protected function ensureHeaders(?array $row=null): void {
 | 
			
		||||
    if ($this->headers !== null || !$this->useHeaders) return;
 | 
			
		||||
@ -87,9 +80,8 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function cookRow(?array $row): ?array {
 | 
			
		||||
    if ($this->cookCtx !== null) {
 | 
			
		||||
      $args = cl::merge([$row], $this->cookArgs);
 | 
			
		||||
      $row = nur_func::_call($this->cookCtx, $args);
 | 
			
		||||
    if ($this->cookFunc !== null) {
 | 
			
		||||
      $row = $this->cookFunc->prependArgs([$row])->invoke();
 | 
			
		||||
    }
 | 
			
		||||
    if ($row !== null) {
 | 
			
		||||
      foreach ($row as &$col) {
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
namespace nulib\output;
 | 
			
		||||
 | 
			
		||||
use nulib\output\std\ProxyMessenger;
 | 
			
		||||
use nulib\php\nur_func;
 | 
			
		||||
use nulib\php\func;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class msg: inscrire un message dans les logs ET l'afficher à l'utilisateur
 | 
			
		||||
@ -39,30 +39,21 @@ class msg extends _messenger {
 | 
			
		||||
    if ($log !== null && $log !== false) {
 | 
			
		||||
      if ($log instanceof IMessenger) log::set_messenger($log);
 | 
			
		||||
      elseif (is_string($log)) log::set_messenger_class($log);
 | 
			
		||||
      elseif (is_array($log)) {
 | 
			
		||||
        nur_func::ensure_class($log, $args);
 | 
			
		||||
        $log = nur_func::cons($log, $args);
 | 
			
		||||
      }
 | 
			
		||||
      else $log = func::call($log);
 | 
			
		||||
      log::set_messenger($log);
 | 
			
		||||
      $msgs[] = $log;
 | 
			
		||||
    }
 | 
			
		||||
    if ($console !== null && $console !== false) {
 | 
			
		||||
      if ($console instanceof IMessenger) console::set_messenger($console);
 | 
			
		||||
      elseif (is_string($console)) console::set_messenger_class($console);
 | 
			
		||||
      elseif (is_array($console)) {
 | 
			
		||||
        nur_func::ensure_class($console, $args);
 | 
			
		||||
        $console = nur_func::cons($console, $args);
 | 
			
		||||
      }
 | 
			
		||||
      else $console = func::call($console);
 | 
			
		||||
      console::set_messenger($console);
 | 
			
		||||
      $msgs[] = $console;
 | 
			
		||||
    }
 | 
			
		||||
    if ($say !== null && $say !== false) {
 | 
			
		||||
      if ($say instanceof IMessenger) say::set_messenger($say);
 | 
			
		||||
      elseif (is_string($say)) say::set_messenger_class($say);
 | 
			
		||||
      elseif (is_array($say)) {
 | 
			
		||||
        nur_func::ensure_class($say, $args);
 | 
			
		||||
        $say = nur_func::cons($say, $args);
 | 
			
		||||
      }
 | 
			
		||||
      else $say = func::call($say);
 | 
			
		||||
      say::set_messenger($say);
 | 
			
		||||
      $msgs[] = $say;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ namespace nulib\php\content;
 | 
			
		||||
 | 
			
		||||
use Closure;
 | 
			
		||||
use nulib\cl;
 | 
			
		||||
use nulib\php\nur_func;
 | 
			
		||||
use nulib\php\func;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class c: classe outil pour gérer du contenu
 | 
			
		||||
@ -62,8 +62,7 @@ class c {
 | 
			
		||||
        # contenu dynamique: le contenu est la valeur de retour de la fonction
 | 
			
		||||
        # ce contenu est rajouté à la suite après avoir été quoté avec self::q()
 | 
			
		||||
        $func = $value;
 | 
			
		||||
        nur_func::ensure_func($func, $object_or_class, $args);
 | 
			
		||||
        $values = self::q(nur_func::call($func, ...$args));
 | 
			
		||||
        $values = self::q(func::call($func));
 | 
			
		||||
        self::add_static_content($dest, $values, $key, $seq);
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
@ -83,16 +82,7 @@ class c {
 | 
			
		||||
            $arg = self::resolve($arg, $object_or_class, false);
 | 
			
		||||
            if (!$array) $arg = $arg[0];
 | 
			
		||||
          }; unset($arg);
 | 
			
		||||
          if (nur_func::is_static($func)) {
 | 
			
		||||
            nur_func::ensure_func($func, $object_or_class, $args);
 | 
			
		||||
            $value = nur_func::call($func, ...$args);
 | 
			
		||||
          } elseif (nur_func::is_class($func)) {
 | 
			
		||||
            nur_func::fix_class_args($func, $args);
 | 
			
		||||
            $value = nur_func::cons($func, ...$args);
 | 
			
		||||
          } else {
 | 
			
		||||
            nur_func::ensure_func($func, $object_or_class, $args);
 | 
			
		||||
            $value = nur_func::call($func, ...$args);
 | 
			
		||||
          }
 | 
			
		||||
          $value = func::with($func, $args)->bind($object_or_class, true)->invoke();
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if ($seq) $dest[] = $value;
 | 
			
		||||
 | 
			
		||||
@ -3,13 +3,16 @@ namespace nulib\php;
 | 
			
		||||
 | 
			
		||||
use Closure;
 | 
			
		||||
use Exception;
 | 
			
		||||
use Generator;
 | 
			
		||||
use nulib\A;
 | 
			
		||||
use nulib\cl;
 | 
			
		||||
use nulib\cv;
 | 
			
		||||
use nulib\StateException;
 | 
			
		||||
use nulib\ValueException;
 | 
			
		||||
use ReflectionClass;
 | 
			
		||||
use ReflectionFunction;
 | 
			
		||||
use ReflectionMethod;
 | 
			
		||||
use Traversable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class func: outils pour appeler fonctions et méthodes dynamiquement
 | 
			
		||||
@ -58,10 +61,7 @@ class func {
 | 
			
		||||
   * la fonction (ne pas uniquement faire une vérification syntaxique)
 | 
			
		||||
   */
 | 
			
		||||
  static function verifix_function(&$func, bool $strict=true, ?string &$reason=null): bool {
 | 
			
		||||
    if ($strict) {
 | 
			
		||||
      $msg = var_export($func, true);
 | 
			
		||||
      $reason = null;
 | 
			
		||||
    }
 | 
			
		||||
    if ($strict) $reason = null;
 | 
			
		||||
    if ($func instanceof ReflectionFunction) return true;
 | 
			
		||||
    if (is_string($func)) {
 | 
			
		||||
      $c = false;
 | 
			
		||||
@ -82,11 +82,11 @@ class func {
 | 
			
		||||
    if ($strict) {
 | 
			
		||||
      $reason = null;
 | 
			
		||||
      if (class_exists($f)) {
 | 
			
		||||
        $reason = "$msg: is a class";
 | 
			
		||||
        $reason = "$f: is a class";
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      if (!function_exists($f)) {
 | 
			
		||||
        $reason = "$msg: function not found";
 | 
			
		||||
        $reason = "$f: function not found";
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
@ -117,10 +117,7 @@ class func {
 | 
			
		||||
   * faire une vérification syntaxique)
 | 
			
		||||
   */
 | 
			
		||||
  static function verifix_class(&$func, bool $strict=true, ?string &$reason=null): bool {
 | 
			
		||||
    if ($strict) {
 | 
			
		||||
      $msg = var_export($func, true);
 | 
			
		||||
      $reason = null;
 | 
			
		||||
    }
 | 
			
		||||
    if ($strict) $reason = null;
 | 
			
		||||
    if ($func instanceof ReflectionClass) return true;
 | 
			
		||||
    if (is_string($func)) {
 | 
			
		||||
      $c = $func;
 | 
			
		||||
@ -138,11 +135,9 @@ class func {
 | 
			
		||||
    if (self::_parse_static($c)) return false;
 | 
			
		||||
    if (self::_parse_method($c)) return false;
 | 
			
		||||
    if ($f !== false) return false;
 | 
			
		||||
    if ($strict) {
 | 
			
		||||
      if (!class_exists($c)) {
 | 
			
		||||
        $reason = "$msg: class not found";
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
    if ($strict && !class_exists($c)) {
 | 
			
		||||
      $reason = "$c: class not found";
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    $func = [$c, false];
 | 
			
		||||
    return true;
 | 
			
		||||
@ -207,10 +202,7 @@ class func {
 | 
			
		||||
   * la méthode est liée (ne pas uniquement faire une vérification syntaxique)
 | 
			
		||||
   */
 | 
			
		||||
  static function verifix_static(&$func, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
 | 
			
		||||
    if ($strict) {
 | 
			
		||||
      $msg = var_export($func, true);
 | 
			
		||||
      $reason = null;
 | 
			
		||||
    }
 | 
			
		||||
    if ($strict) $reason = null;
 | 
			
		||||
    if ($func instanceof ReflectionMethod) {
 | 
			
		||||
      $bound = false;
 | 
			
		||||
      return true;
 | 
			
		||||
@ -265,18 +257,19 @@ class func {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    if ($strict) {
 | 
			
		||||
      [$c, $f] = $cf;
 | 
			
		||||
      $reason = null;
 | 
			
		||||
      if ($bound) {
 | 
			
		||||
        if (!class_exists($c)) {
 | 
			
		||||
          $reason = "$msg: class not found";
 | 
			
		||||
          $reason = "$c: class not found";
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
        if (!method_exists($c, $f)) {
 | 
			
		||||
          $reason = "$msg: method not found";
 | 
			
		||||
          $reason = "$c::$f: method not found";
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        $reason = "$msg: not bound";
 | 
			
		||||
        $reason = "$c::$f: not bound";
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $func = $cf;
 | 
			
		||||
@ -342,10 +335,7 @@ class func {
 | 
			
		||||
   * la méthode est liée (ne pas uniquement faire une vérification syntaxique)
 | 
			
		||||
   */
 | 
			
		||||
  static function verifix_method(&$func, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
 | 
			
		||||
    if ($strict) {
 | 
			
		||||
      $msg = var_export($func, true);
 | 
			
		||||
      $reason = null;
 | 
			
		||||
    }
 | 
			
		||||
    if ($strict) $reason = null;
 | 
			
		||||
    if ($func instanceof ReflectionMethod) {
 | 
			
		||||
      $bound = false;
 | 
			
		||||
      return true;
 | 
			
		||||
@ -401,18 +391,19 @@ class func {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    if ($strict) {
 | 
			
		||||
      [$c, $f] = $cf;
 | 
			
		||||
      $reason = null;
 | 
			
		||||
      if ($bound) {
 | 
			
		||||
        if (!is_object($c) && !class_exists($c)) {
 | 
			
		||||
          $reason = "$msg: class not found";
 | 
			
		||||
          $reason = "$c: class not found";
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
        if (!method_exists($c, $f)) {
 | 
			
		||||
          $reason = "$msg: method not found";
 | 
			
		||||
          $reason = "$c::$f: method not found";
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        $reason = "$msg: not bound";
 | 
			
		||||
        $reason = "$c::$f: not bound";
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $func = $cf;
 | 
			
		||||
@ -446,7 +437,7 @@ class func {
 | 
			
		||||
    return new ValueException($reason);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static function with($func, ?array $args=null, bool $strict=true): self {
 | 
			
		||||
  private static function _with($func, ?array $args=null, bool $strict=true, ?string &$reason=null): ?self {
 | 
			
		||||
    if (!is_array($func)) {
 | 
			
		||||
      if ($func instanceof Closure) {
 | 
			
		||||
        return new self(self::TYPE_CLOSURE, $func, $args);
 | 
			
		||||
@ -467,6 +458,12 @@ class func {
 | 
			
		||||
    } elseif (self::verifix_static($func, $strict, $bound, $reason)) {
 | 
			
		||||
      return new self(self::TYPE_STATIC, $func, $args, $bound, $reason);
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static function with($func, ?array $args=null, bool $strict=true): self {
 | 
			
		||||
    $func = self::_with($func, $args, $strict, $reason);
 | 
			
		||||
    if ($func !== null) return $func;
 | 
			
		||||
    throw self::not_a_callable($func, $reason);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -487,10 +484,45 @@ class func {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static function is_callable($func): bool {
 | 
			
		||||
    $func = self::_with($func);
 | 
			
		||||
    if ($func === null) return false;
 | 
			
		||||
    if (!$func->isBound()) return false;
 | 
			
		||||
    return $func->type !== self::TYPE_CLASS;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static function call($func, ...$args) {
 | 
			
		||||
    return self::with($func)->invoke($args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * si $value est une fonction, l'appeler
 | 
			
		||||
   * si $value ou le résultat de l'appel est un Traversable, le résoudre
 | 
			
		||||
   * sinon retourner $value tel quel
 | 
			
		||||
   *
 | 
			
		||||
   * en définitive, la valeur de retour de cette fonction est soit un scalaire,
 | 
			
		||||
   * soit un array, soit un objet qui n'est pas Traversable
 | 
			
		||||
   * @return mixed
 | 
			
		||||
   */
 | 
			
		||||
  static function get_value($value, ...$args) {
 | 
			
		||||
    if ($value instanceof self) $value = $value->invoke($args);
 | 
			
		||||
    elseif (is_callable($value)) $value = self::call($value, ...$args);
 | 
			
		||||
    if ($value instanceof Traversable) $value = cl::all($value);
 | 
			
		||||
    return $value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * si $value est une fonction, l'appeler
 | 
			
		||||
   * si $value ou le résultat de l'appel est un Traversable, le retourner
 | 
			
		||||
   * sinon retourner $value en tant qu'array
 | 
			
		||||
   */
 | 
			
		||||
  static function get_iterable($value, ...$args): ?iterable {
 | 
			
		||||
    if ($value instanceof self) $value = $value->invoke($args);
 | 
			
		||||
    elseif (is_callable($value)) $value = self::call($value, ...$args);
 | 
			
		||||
    if ($value instanceof Traversable) return $value;
 | 
			
		||||
    else return cl::withn($value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #############################################################################
 | 
			
		||||
 | 
			
		||||
  protected function __construct(int $type, $func, ?array $args=null, bool $bound=false, ?string $reason=null) {
 | 
			
		||||
@ -561,6 +593,27 @@ class func {
 | 
			
		||||
 | 
			
		||||
  protected int $maxArgs;
 | 
			
		||||
 | 
			
		||||
  function replaceArgs(?array $args): self {
 | 
			
		||||
    $this->prefixArgs = $args?? [];
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function prependArgs(?array $args, ?int $stripCount=null): self {
 | 
			
		||||
    if ($stripCount !== null || $args !== null) {
 | 
			
		||||
      array_splice($this->prefixArgs, 0, $stripCount ?? 0, $args);
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function appendArgs(?array $args, ?int $stripCount=null): self {
 | 
			
		||||
    if ($stripCount !== null || $args !== null) {
 | 
			
		||||
      $stripCount ??= 0;
 | 
			
		||||
      if ($stripCount > 0) array_splice($this->prefixArgs, -$stripCount);
 | 
			
		||||
      $this->prefixArgs = array_merge($this->prefixArgs, $args);
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function updateReflection($reflection): void {
 | 
			
		||||
    $variadic = false;
 | 
			
		||||
    $minArgs = $maxArgs = 0;
 | 
			
		||||
@ -596,11 +649,16 @@ class func {
 | 
			
		||||
    else return $this->bound && $this->object !== null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function bind($object): self {
 | 
			
		||||
  function bind($object, bool $unlessAlreadyBound=false, bool $replace=false): self {
 | 
			
		||||
    if ($this->type !== self::TYPE_METHOD) return $this;
 | 
			
		||||
    if ($this->bound && $unlessAlreadyBound) return $this;
 | 
			
		||||
 | 
			
		||||
    [$c, $f] = $this->func;
 | 
			
		||||
    if ($this->reflection === null) {
 | 
			
		||||
    if ($replace) {
 | 
			
		||||
      $c = $object;
 | 
			
		||||
      $this->func = [$c, $f];
 | 
			
		||||
      $this->updateReflection(new ReflectionMethod($c, $f));
 | 
			
		||||
    } elseif ($this->reflection === null) {
 | 
			
		||||
      $this->func[0] = $c = $object;
 | 
			
		||||
      $this->updateReflection(new ReflectionMethod($c, $f));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -44,7 +44,7 @@ class mprop {
 | 
			
		||||
    } catch (ReflectionException $e) {
 | 
			
		||||
      return oprop::get($object, $property, $default);
 | 
			
		||||
    }
 | 
			
		||||
    return nur_func::call([$object, $m], $default);
 | 
			
		||||
    return func::call([$object, $m], $default);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** spécifier la valeur d'une propriété */
 | 
			
		||||
@ -60,7 +60,7 @@ class mprop {
 | 
			
		||||
    } catch (ReflectionException $e) {
 | 
			
		||||
      return oprop::_set($c, $object, $property, $value);
 | 
			
		||||
    }
 | 
			
		||||
    nur_func::call([$object, $m], $value);
 | 
			
		||||
    func::call([$object, $m], $value);
 | 
			
		||||
    return $value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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"],
 | 
			
		||||
    "formatter_func" => ["?callable", null, "fonction qui formatte la valeur pour affichage"],
 | 
			
		||||
    "format" => [null, null, "format à utiliser pour l'affichage"],
 | 
			
		||||
    "size" => ["?int", null, "nom de caractères ou de chiffres de la valeur"],
 | 
			
		||||
    "precision" => ["?int", null, "nombre de chiffres après la virgule pour une valeur numérique flottante"],
 | 
			
		||||
    "" => ["array", ["scalar"], "nature du schéma",
 | 
			
		||||
      "schema" => self::NATURE_METASCHEMA,
 | 
			
		||||
    ],
 | 
			
		||||
@ -37,25 +39,48 @@ class ref_schema {
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  const MESSAGES = [
 | 
			
		||||
    "missing" => "Vous devez spécifier cette valeur",
 | 
			
		||||
    "unavailable" => "Vous devez spécifier cette valeur",
 | 
			
		||||
    "null" => "Cette valeur ne doit pas être nulle",
 | 
			
		||||
    "empty" => "Cette valeur ne doit pas être vide",
 | 
			
		||||
    "invalid" => "Cette valeur est invalide",
 | 
			
		||||
    "missing" => "vous devez spécifier cette valeur",
 | 
			
		||||
    "unavailable" => "vous devez spécifier cette valeur",
 | 
			
		||||
    "null" => "cette valeur ne doit pas être nulle",
 | 
			
		||||
    "empty" => "cette valeur ne doit pas être vide",
 | 
			
		||||
    "invalid" => "cette valeur est invalide",
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  const PARAMS_SCHEMA = [
 | 
			
		||||
    "analyze" => ["bool", true, "faut-il analyser la valeur?"],
 | 
			
		||||
    "reanalyze" => ["bool", true, "faut-il forcer l'analyse de la valeur?"],
 | 
			
		||||
    "normalize" => ["bool", true, "faut-il normaliser la valeur?"],
 | 
			
		||||
    "renormalize" => ["bool", true, "faut-il forcer la normalisation de la valeur?"],
 | 
			
		||||
    "throw" => ["bool", true, "faut-il lancer une exception en cas d'erreur?"],
 | 
			
		||||
    //...ref_input::INPUT_PARAMS_SCHEMA,
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /** @var array clés supplémentaires de schéma de la nature scalaire */
 | 
			
		||||
  const SCALAR_NATURE_METASCHEMA = [
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  const SCALAR_PARAMS_SCHEMA = [
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /** @var array clés supplémentaires de schéma de la nature associative */
 | 
			
		||||
  const ASSOC_NATURE_METASCHEMA = [
 | 
			
		||||
    "ensure_array" => ["bool", false, "faut-il s'assurer que le tableau destination est non nul?"],
 | 
			
		||||
    "ensure_keys" => ["bool", true, "faut-il s'assurer que toutes les clés existent?"],
 | 
			
		||||
    "ensure_order" => ["bool", true, "faut-il s'assurer que les clés soient dans l'ordre?"],
 | 
			
		||||
    "ensure_array" => ["bool", null, "faut-il s'assurer que le tableau destination est non nul?"],
 | 
			
		||||
    "ensure_assoc" => ["bool", null, "faut-il s'assurer que le tableau destination est associatif?"],
 | 
			
		||||
    "ensure_keys" => ["bool", null, "faut-il s'assurer que toutes les clés existent avec la valeur par défaut?"],
 | 
			
		||||
    "ensure_order" => ["bool", null, "faut-il s'assurer que les clés soient dans l'ordre?"],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  const ASSOC_PARAMS_SCHEMA = [
 | 
			
		||||
    "ensure_array" => ["bool", false],
 | 
			
		||||
    "ensure_assoc" => ["bool", true],
 | 
			
		||||
    "ensure_keys" => ["bool", true],
 | 
			
		||||
    "ensure_order" => ["bool", true],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /** @var array clés supplémentaires de schéma de la nature liste */
 | 
			
		||||
  const LIST_NATURE_METASCHEMA = [
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  const LIST_PARAMS_SCHEMA = [
 | 
			
		||||
  ];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -242,6 +242,21 @@ class str {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * vérifier si $s a le préfixe $prefix
 | 
			
		||||
   * - si $prefix commence par /, c'est une expression régulière, et elle doit
 | 
			
		||||
   * matcher $s
 | 
			
		||||
   * - sinon $s doit commencer par la chaine $prefix
 | 
			
		||||
   */
 | 
			
		||||
  static final function match_prefix(?string $s, ?string $prefix): bool {
 | 
			
		||||
    if ($s === null || $prefix === null) return false;
 | 
			
		||||
    if (substr($prefix, 0, 1) === "/") {
 | 
			
		||||
      return preg_match($prefix, $s);
 | 
			
		||||
    } else {
 | 
			
		||||
      return self::_starts_with($prefix, $s);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * ajouter $sep$prefix$text$suffix à $s si $text est non vide
 | 
			
		||||
   *
 | 
			
		||||
 | 
			
		||||
@ -78,7 +78,7 @@ class ComposerFile {
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  function selectProfile(string $profile, ComposerPmanFile $config): void {
 | 
			
		||||
    $config = $config->getProfileConfig($profile);
 | 
			
		||||
    $config = $config->getProfileConfig($profile, $this->getRequires(), $this->getRequireDevs());
 | 
			
		||||
    // corriger les liens
 | 
			
		||||
    $deps = cl::merge(array_keys($config["require"]), array_keys($config["require-dev"]));
 | 
			
		||||
    $paths = [];
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@ namespace nulib\tools\pman;
 | 
			
		||||
use nulib\A;
 | 
			
		||||
use nulib\ext\yaml;
 | 
			
		||||
use nulib\os\path;
 | 
			
		||||
use nulib\str;
 | 
			
		||||
use nulib\ValueException;
 | 
			
		||||
 | 
			
		||||
class ComposerPmanFile {
 | 
			
		||||
@ -49,6 +50,8 @@ class ComposerPmanFile {
 | 
			
		||||
      $composer =& $data["composer"];
 | 
			
		||||
      A::ensure_array($composer);
 | 
			
		||||
      A::ensure_array($composer["profiles"]);
 | 
			
		||||
      A::ensure_array($composer["match_require"]);
 | 
			
		||||
      A::ensure_array($composer["match_require-dev"]);
 | 
			
		||||
      foreach ($composer["profiles"] as $profileName) {
 | 
			
		||||
        $profile =& $composer[$profileName];
 | 
			
		||||
        A::ensure_array($profile);
 | 
			
		||||
@ -61,11 +64,43 @@ class ComposerPmanFile {
 | 
			
		||||
    return $this->data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getProfileConfig(string $profile): array {
 | 
			
		||||
  function getProfileConfig(string $profile, ?array $composerRequires=null, ?array $composerRequireDevs=null): array {
 | 
			
		||||
    $config = $this->data["composer"][$profile] ?? null;
 | 
			
		||||
    if ($config === null) {
 | 
			
		||||
      throw new ValueException("$profile: profil invalide");
 | 
			
		||||
    }
 | 
			
		||||
    if ($composerRequires !== null) {
 | 
			
		||||
      $matchRequires = $this->data["composer"]["match_require"];
 | 
			
		||||
      foreach ($composerRequires as $dep => $version) {
 | 
			
		||||
        $found = false;
 | 
			
		||||
        foreach ($matchRequires as $matchRequire) {
 | 
			
		||||
          if (str::match_prefix($dep, $matchRequire)) {
 | 
			
		||||
            $found = true;
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        $require = $config["require"][$dep] ?? null;
 | 
			
		||||
        if ($found && $require === null) {
 | 
			
		||||
          $config["require"][$dep] = $version;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if ($composerRequireDevs !== null) {
 | 
			
		||||
      $matchRequireDevs = $this->data["composer"]["match_require-dev"];
 | 
			
		||||
      foreach ($composerRequireDevs as $dep => $version) {
 | 
			
		||||
        $found = false;
 | 
			
		||||
        foreach ($matchRequireDevs as $matchRequireDev) {
 | 
			
		||||
          if (str::match_prefix($dep, $matchRequireDev)) {
 | 
			
		||||
            $found = true;
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        $requireDev = $config["require-dev"][$dep] ?? null;
 | 
			
		||||
        if ($found && $requireDev === null) {
 | 
			
		||||
          $config["require"][$dep] = $version;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $config;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,132 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nulib {
 | 
			
		||||
  use nulib\tests\TestCase;
 | 
			
		||||
  use nulib\impl\config;
 | 
			
		||||
  use nulib\impl\myapp;
 | 
			
		||||
  use nulib\impl\MyApplication1;
 | 
			
		||||
  use nulib\impl\MyApplication2;
 | 
			
		||||
 | 
			
		||||
  class appTest extends TestCase {
 | 
			
		||||
    function testWith() {
 | 
			
		||||
      $projdir = config::get_projdir();
 | 
			
		||||
      $cwd = getcwd();
 | 
			
		||||
 | 
			
		||||
      myapp::reset();
 | 
			
		||||
      $app1 = myapp::with(MyApplication1::class);
 | 
			
		||||
      self::assertSame([
 | 
			
		||||
        "projdir" => $projdir,
 | 
			
		||||
        "vendor" => [
 | 
			
		||||
          "bindir" => "$projdir/vendor/bin",
 | 
			
		||||
          "autoload" => "$projdir/vendor/autoload.php",
 | 
			
		||||
        ],
 | 
			
		||||
        "appcode" => "nur-sery",
 | 
			
		||||
        "cwd" => $cwd,
 | 
			
		||||
        "datadir" => "$projdir/devel",
 | 
			
		||||
        "etcdir" => "$projdir/devel/etc",
 | 
			
		||||
        "vardir" => "$projdir/devel/var",
 | 
			
		||||
        "logdir" => "$projdir/devel/log",
 | 
			
		||||
        "profile" => "devel",
 | 
			
		||||
        "appgroup" => null,
 | 
			
		||||
        "name" => "my-application1",
 | 
			
		||||
        "title" => null,
 | 
			
		||||
      ], $app1->getParams());
 | 
			
		||||
 | 
			
		||||
      $app2 = myapp::with(MyApplication2::class, $app1);
 | 
			
		||||
      self::assertSame([
 | 
			
		||||
        "projdir" => $projdir,
 | 
			
		||||
        "vendor" => [
 | 
			
		||||
          "bindir" => "$projdir/vendor/bin",
 | 
			
		||||
          "autoload" => "$projdir/vendor/autoload.php",
 | 
			
		||||
        ],
 | 
			
		||||
        "appcode" => "nur-sery",
 | 
			
		||||
        "cwd" => $cwd,
 | 
			
		||||
        "datadir" => "$projdir/devel",
 | 
			
		||||
        "etcdir" => "$projdir/devel/etc",
 | 
			
		||||
        "vardir" => "$projdir/devel/var",
 | 
			
		||||
        "logdir" => "$projdir/devel/log",
 | 
			
		||||
        "profile" => "devel",
 | 
			
		||||
        "appgroup" => null,
 | 
			
		||||
        "name" => "my-application2",
 | 
			
		||||
        "title" => null,
 | 
			
		||||
      ], $app2->getParams());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function testInit() {
 | 
			
		||||
      $projdir = config::get_projdir();
 | 
			
		||||
      $cwd = getcwd();
 | 
			
		||||
 | 
			
		||||
      myapp::reset();
 | 
			
		||||
      myapp::init(MyApplication1::class);
 | 
			
		||||
      self::assertSame([
 | 
			
		||||
        "projdir" => $projdir,
 | 
			
		||||
        "vendor" => [
 | 
			
		||||
          "bindir" => "$projdir/vendor/bin",
 | 
			
		||||
          "autoload" => "$projdir/vendor/autoload.php",
 | 
			
		||||
        ],
 | 
			
		||||
        "appcode" => "nur-sery",
 | 
			
		||||
        "cwd" => $cwd,
 | 
			
		||||
        "datadir" => "$projdir/devel",
 | 
			
		||||
        "etcdir" => "$projdir/devel/etc",
 | 
			
		||||
        "vardir" => "$projdir/devel/var",
 | 
			
		||||
        "logdir" => "$projdir/devel/log",
 | 
			
		||||
        "profile" => "devel",
 | 
			
		||||
        "appgroup" => null,
 | 
			
		||||
        "name" => "my-application1",
 | 
			
		||||
        "title" => null,
 | 
			
		||||
      ], myapp::get()->getParams());
 | 
			
		||||
 | 
			
		||||
      myapp::init(MyApplication2::class);
 | 
			
		||||
      self::assertSame([
 | 
			
		||||
        "projdir" => $projdir,
 | 
			
		||||
        "vendor" => [
 | 
			
		||||
          "bindir" => "$projdir/vendor/bin",
 | 
			
		||||
          "autoload" => "$projdir/vendor/autoload.php",
 | 
			
		||||
        ],
 | 
			
		||||
        "appcode" => "nur-sery",
 | 
			
		||||
        "cwd" => $cwd,
 | 
			
		||||
        "datadir" => "$projdir/devel",
 | 
			
		||||
        "etcdir" => "$projdir/devel/etc",
 | 
			
		||||
        "vardir" => "$projdir/devel/var",
 | 
			
		||||
        "logdir" => "$projdir/devel/log",
 | 
			
		||||
        "profile" => "devel",
 | 
			
		||||
        "appgroup" => null,
 | 
			
		||||
        "name" => "my-application2",
 | 
			
		||||
        "title" => null,
 | 
			
		||||
      ], myapp::get()->getParams());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace nulib\impl {
 | 
			
		||||
 | 
			
		||||
  use nulib\app\cli\Application;
 | 
			
		||||
  use nulib\os\path;
 | 
			
		||||
  use nulib\app;
 | 
			
		||||
 | 
			
		||||
  class config {
 | 
			
		||||
    const PROJDIR = __DIR__.'/..';
 | 
			
		||||
 | 
			
		||||
    static function get_projdir(): string {
 | 
			
		||||
      return path::abspath(self::PROJDIR);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  class myapp extends app {
 | 
			
		||||
    static function reset(): void {
 | 
			
		||||
      self::$app = null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  class MyApplication1 extends Application {
 | 
			
		||||
    const PROJDIR = config::PROJDIR;
 | 
			
		||||
 | 
			
		||||
    function main() {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  class MyApplication2 extends Application {
 | 
			
		||||
    const PROJDIR = null;
 | 
			
		||||
 | 
			
		||||
    function main() {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -118,7 +118,7 @@ class SqliteStorageTest extends TestCase {
 | 
			
		||||
      const COLUMN_DEFINITIONS = [
 | 
			
		||||
        "a__" => "varchar",
 | 
			
		||||
        "b__" => "varchar",
 | 
			
		||||
        "b__sum_" => self::SUM_DEFINITION,
 | 
			
		||||
        "b__sum_" => "sersum",
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      function getItemValues($item): ?array {
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ class SqliteTest extends TestCase {
 | 
			
		||||
 | 
			
		||||
  function testMigration() {
 | 
			
		||||
    $sqlite = new Sqlite(":memory:", [
 | 
			
		||||
      "migrate" => [
 | 
			
		||||
      "migration" => [
 | 
			
		||||
        self::CREATE_PERSON,
 | 
			
		||||
        self::INSERT_JEPHTE,
 | 
			
		||||
      ],
 | 
			
		||||
@ -49,7 +49,7 @@ class SqliteTest extends TestCase {
 | 
			
		||||
  }
 | 
			
		||||
  function testInsert() {
 | 
			
		||||
    $sqlite = new Sqlite(":memory:", [
 | 
			
		||||
      "migrate" => "create table mapping (i integer, s varchar)",
 | 
			
		||||
      "migration" => "create table mapping (i integer, s varchar)",
 | 
			
		||||
    ]);
 | 
			
		||||
    $sqlite->exec(["insert into mapping", "values" => ["i" => 1, "s" => "un"]]);
 | 
			
		||||
    $sqlite->exec(["insert mapping", "values" => ["i" => 2, "s" => "deux"]]);
 | 
			
		||||
@ -78,7 +78,7 @@ class SqliteTest extends TestCase {
 | 
			
		||||
 | 
			
		||||
  function testSelect() {
 | 
			
		||||
    $sqlite = new Sqlite(":memory:", [
 | 
			
		||||
      "migrate" => "create table user (name varchar, amount integer)",
 | 
			
		||||
      "migration" => "create table user (name varchar, amount integer)",
 | 
			
		||||
    ]);
 | 
			
		||||
    $sqlite->exec(["insert into user", "values" => ["name" => "jclain1", "amount" => 1]]);
 | 
			
		||||
    $sqlite->exec(["insert into user", "values" => ["name" => "jclain2", "amount" => 2]]);
 | 
			
		||||
@ -130,7 +130,7 @@ class SqliteTest extends TestCase {
 | 
			
		||||
 | 
			
		||||
  function testSelectGroupBy() {
 | 
			
		||||
    $sqlite = new Sqlite(":memory:", [
 | 
			
		||||
      "migrate" => "create table user (name varchar, amount integer)",
 | 
			
		||||
      "migration" => "create table user (name varchar, amount integer)",
 | 
			
		||||
    ]);
 | 
			
		||||
    $sqlite->exec(["insert into user", "values" => ["name" => "jclain1", "amount" => 1]]);
 | 
			
		||||
    $sqlite->exec(["insert into user", "values" => ["name" => "jclain2", "amount" => 1]]);
 | 
			
		||||
 | 
			
		||||
@ -6,119 +6,119 @@ use PHPUnit\Framework\TestCase;
 | 
			
		||||
class _queryTest extends TestCase {
 | 
			
		||||
  function testParseConds(): void {
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_conds(null, $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_conds(null, $sql, $params);
 | 
			
		||||
    self::assertNull($sql);
 | 
			
		||||
    self::assertNull($params);
 | 
			
		||||
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_conds([], $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_conds([], $sql, $params);
 | 
			
		||||
    self::assertNull($sql);
 | 
			
		||||
    self::assertNull($params);
 | 
			
		||||
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_conds(["col" => null], $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_conds(["col" => null], $sql, $params);
 | 
			
		||||
    self::assertSame(["col is null"], $sql);
 | 
			
		||||
    self::assertNull($params);
 | 
			
		||||
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_conds(["col = 'value'"], $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_conds(["col = 'value'"], $sql, $params);
 | 
			
		||||
    self::assertSame(["col = 'value'"], $sql);
 | 
			
		||||
    self::assertNull($params);
 | 
			
		||||
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_conds([["col = 'value'"]], $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_conds([["col = 'value'"]], $sql, $params);
 | 
			
		||||
    self::assertSame(["col = 'value'"], $sql);
 | 
			
		||||
    self::assertNull($params);
 | 
			
		||||
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_conds(["int" => 42, "string" => "value"], $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_conds(["int" => 42, "string" => "value"], $sql, $params);
 | 
			
		||||
    self::assertSame(["(int = :int and string = :string)"], $sql);
 | 
			
		||||
    self::assertSame(["int" => 42, "string" => "value"], $params);
 | 
			
		||||
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_conds(["or", "int" => 42, "string" => "value"], $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_conds(["or", "int" => 42, "string" => "value"], $sql, $params);
 | 
			
		||||
    self::assertSame(["(int = :int or string = :string)"], $sql);
 | 
			
		||||
    self::assertSame(["int" => 42, "string" => "value"], $params);
 | 
			
		||||
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_conds([["int" => 42, "string" => "value"], ["int" => 24, "string" => "eulav"]], $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_conds([["int" => 42, "string" => "value"], ["int" => 24, "string" => "eulav"]], $sql, $params);
 | 
			
		||||
    self::assertSame(["((int = :int and string = :string) and (int = :int2 and string = :string2))"], $sql);
 | 
			
		||||
    self::assertSame(["int" => 42, "string" => "value", "int2" => 24, "string2" => "eulav"], $params);
 | 
			
		||||
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_conds(["int" => ["is null"], "string" => ["<>", "value"]], $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_conds(["int" => ["is null"], "string" => ["<>", "value"]], $sql, $params);
 | 
			
		||||
    self::assertSame(["(int is null and string <> :string)"], $sql);
 | 
			
		||||
    self::assertSame(["string" => "value"], $params);
 | 
			
		||||
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_conds(["col" => ["between", "lower", "upper"]], $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_conds(["col" => ["between", "lower", "upper"]], $sql, $params);
 | 
			
		||||
    self::assertSame(["col between :col and :col2"], $sql);
 | 
			
		||||
    self::assertSame(["col" => "lower", "col2" => "upper"], $params);
 | 
			
		||||
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_conds(["col" => ["in", "one"]], $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_conds(["col" => ["in", "one"]], $sql, $params);
 | 
			
		||||
    self::assertSame(["col in (:col)"], $sql);
 | 
			
		||||
    self::assertSame(["col" => "one"], $params);
 | 
			
		||||
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_conds(["col" => ["in", ["one", "two"]]], $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_conds(["col" => ["in", ["one", "two"]]], $sql, $params);
 | 
			
		||||
    self::assertSame(["col in (:col, :col2)"], $sql);
 | 
			
		||||
    self::assertSame(["col" => "one", "col2" => "two"], $params);
 | 
			
		||||
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_conds(["col" => ["=", ["one", "two"]]], $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_conds(["col" => ["=", ["one", "two"]]], $sql, $params);
 | 
			
		||||
    self::assertSame(["col = :col and col = :col2"], $sql);
 | 
			
		||||
    self::assertSame(["col" => "one", "col2" => "two"], $params);
 | 
			
		||||
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_conds(["or", "col" => ["=", ["one", "two"]]], $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_conds(["or", "col" => ["=", ["one", "two"]]], $sql, $params);
 | 
			
		||||
    self::assertSame(["col = :col or col = :col2"], $sql);
 | 
			
		||||
    self::assertSame(["col" => "one", "col2" => "two"], $params);
 | 
			
		||||
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_conds(["col" => ["<>", ["one", "two"]]], $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_conds(["col" => ["<>", ["one", "two"]]], $sql, $params);
 | 
			
		||||
    self::assertSame(["col <> :col and col <> :col2"], $sql);
 | 
			
		||||
    self::assertSame(["col" => "one", "col2" => "two"], $params);
 | 
			
		||||
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_conds(["or", "col" => ["<>", ["one", "two"]]], $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_conds(["or", "col" => ["<>", ["one", "two"]]], $sql, $params);
 | 
			
		||||
    self::assertSame(["col <> :col or col <> :col2"], $sql);
 | 
			
		||||
    self::assertSame(["col" => "one", "col2" => "two"], $params);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function testParseValues(): void {
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_set_values(null, $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_set_values(null, $sql, $params);
 | 
			
		||||
    self::assertNull($sql);
 | 
			
		||||
    self::assertNull($params);
 | 
			
		||||
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_set_values([], $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_set_values([], $sql, $params);
 | 
			
		||||
    self::assertNull($sql);
 | 
			
		||||
    self::assertNull($params);
 | 
			
		||||
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_set_values(["col = 'value'"], $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_set_values(["col = 'value'"], $sql, $params);
 | 
			
		||||
    self::assertSame(["col = 'value'"], $sql);
 | 
			
		||||
    self::assertNull($params);
 | 
			
		||||
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_set_values([["col = 'value'"]], $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_set_values([["col = 'value'"]], $sql, $params);
 | 
			
		||||
    self::assertSame(["col = 'value'"], $sql);
 | 
			
		||||
    self::assertNull($params);
 | 
			
		||||
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_set_values(["int" => 42, "string" => "value"], $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_set_values(["int" => 42, "string" => "value"], $sql, $params);
 | 
			
		||||
    self::assertSame(["int = :int", "string = :string"], $sql);
 | 
			
		||||
    self::assertSame(["int" => 42, "string" => "value"], $params);
 | 
			
		||||
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_set_values(["int" => 42, "string" => "value"], $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_set_values(["int" => 42, "string" => "value"], $sql, $params);
 | 
			
		||||
    self::assertSame(["int = :int", "string = :string"], $sql);
 | 
			
		||||
    self::assertSame(["int" => 42, "string" => "value"], $params);
 | 
			
		||||
 | 
			
		||||
    $sql = $params = null;
 | 
			
		||||
    _query_base::parse_set_values([["int" => 42, "string" => "value"], ["int" => 24, "string" => "eulav"]], $sql, $params);
 | 
			
		||||
    _sqliteQuery::parse_set_values([["int" => 42, "string" => "value"], ["int" => 24, "string" => "eulav"]], $sql, $params);
 | 
			
		||||
    self::assertSame(["int = :int", "string = :string", "int = :int2", "string = :string2"], $sql);
 | 
			
		||||
    self::assertSame(["int" => 42, "string" => "value", "int2" => 24, "string2" => "eulav"], $params);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -1103,11 +1103,34 @@ namespace nulib\php {
 | 
			
		||||
 | 
			
		||||
    function testRebind() {
 | 
			
		||||
      $func = func::with([C1::class, "tmethod"]);
 | 
			
		||||
      // objets du même type
 | 
			
		||||
      self::assertSame(11, $func->bind(new C1(0))->invoke());
 | 
			
		||||
      self::assertSame(12, $func->bind(new C1(1))->invoke());
 | 
			
		||||
      // objets de type différent
 | 
			
		||||
      self::assertException(ValueException::class, function() use ($func) {
 | 
			
		||||
        $func->bind(new C0())->invoke();
 | 
			
		||||
      });
 | 
			
		||||
      self::assertSame(11, $func->bind(new C0(), false, true)->invoke());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function testModifyArgs() {
 | 
			
		||||
      $closure = function(...$args) { return $args; };
 | 
			
		||||
 | 
			
		||||
      self::assertSame(["x", "y", "z"], func::with($closure, ["a", "b", "c"])->replaceArgs(["x", "y", "z"])->invoke());
 | 
			
		||||
 | 
			
		||||
      self::assertSame(["x", "y", "z", "a", "b", "c"], func::with($closure, ["a", "b", "c"])->prependArgs(["x", "y", "z"])->invoke());
 | 
			
		||||
      self::assertSame(["x", "y", "z", "a", "b", "c"], func::with($closure, ["a", "b", "c"])->prependArgs(["x", "y", "z"], 0)->invoke());
 | 
			
		||||
      self::assertSame(["x", "y", "z", "b", "c"], func::with($closure, ["a", "b", "c"])->prependArgs(["x", "y", "z"], 1)->invoke());
 | 
			
		||||
      self::assertSame(["x", "y", "z", "c"], func::with($closure, ["a", "b", "c"])->prependArgs(["x", "y", "z"], 2)->invoke());
 | 
			
		||||
      self::assertSame(["x", "y", "z"], func::with($closure, ["a", "b", "c"])->prependArgs(["x", "y", "z"], 3)->invoke());
 | 
			
		||||
      self::assertSame(["x", "y", "z"], func::with($closure, ["a", "b", "c"])->prependArgs(["x", "y", "z"], 4)->invoke());
 | 
			
		||||
 | 
			
		||||
      self::assertSame(["a", "b", "c", "x", "y", "z"], func::with($closure, ["a", "b", "c"])->appendArgs(["x", "y", "z"])->invoke());
 | 
			
		||||
      self::assertSame(["a", "b", "c", "x", "y", "z"], func::with($closure, ["a", "b", "c"])->appendArgs(["x", "y", "z"], 0)->invoke());
 | 
			
		||||
      self::assertSame(["a", "b", "x", "y", "z"], func::with($closure, ["a", "b", "c"])->appendArgs(["x", "y", "z"], 1)->invoke());
 | 
			
		||||
      self::assertSame(["a", "x", "y", "z"], func::with($closure, ["a", "b", "c"])->appendArgs(["x", "y", "z"], 2)->invoke());
 | 
			
		||||
      self::assertSame(["x", "y", "z"], func::with($closure, ["a", "b", "c"])->appendArgs(["x", "y", "z"], 3)->invoke());
 | 
			
		||||
      self::assertSame(["x", "y", "z"], func::with($closure, ["a", "b", "c"])->appendArgs(["x", "y", "z"], 4)->invoke());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user