diff --git a/nur_bin/mysql-storage.php b/nur_bin/mysql-storage.php index d794a12..0ea0ab1 100755 --- a/nur_bin/mysql-storage.php +++ b/nur_bin/mysql-storage.php @@ -21,16 +21,21 @@ Application::run(new class extends Application { ["-d", "--dbconn", "args" => 1, "help" => "nom de la connexion à la base de données", ], - ["-c", "--channel", "args" => 1, + ["-t", "--table-name", "args" => 1, "help" => "nom de la table porteuse du canal de données", ], + ["-c", "--channel-class", "args" => 1, + "help" => "nom de la classe dérivée de CapacitorChannel", + ], ]; - protected $dbconn; + protected ?string $dbconn = null; - protected $channel; + protected ?string $tableName = null; - protected $args; + protected ?string $channelClass = null; + + protected ?array $args = null; protected static function isa_cond(string $arg, ?array &$ms=null): bool { return preg_match('/^(.+?)\s*(=|<>|<|>|<=|>=|(?:is\s+)?null|(?:is\s+)?not\s+null)\s*(.*)$/', $arg, $ms); @@ -43,15 +48,21 @@ Application::run(new class extends Application { if ($tmp === null) self::die("$dbconn: base de données invalide"); $dbconn = $tmp; - if ($this->channel === null) self::die("Vous devez spécifier le canal de données"); + if ($this->channelClass !== null) { + $channelClass = str_replace("/", "\\", $this->channelClass); + $channel = new $channelClass; + } elseif ($this->tableName !== null) { + $channel = new class($this->tableName) extends CapacitorChannel { + public function __construct(?string $name=null) { + parent::__construct($name); + $this->tableName = $name; + } + }; + } else { + self::die("Vous devez spécifier le canal de données"); + } $storage = new MysqlStorage($dbconn); - $channel = new class($this->channel) extends CapacitorChannel { - public function __construct(?string $name=null) { - parent::__construct($name); - $this->tableName = $name; - } - }; $capacitor = new Capacitor($storage, $channel); $args = $this->args; diff --git a/nur_bin/sqlite-storage.php b/nur_bin/sqlite-storage.php index 18b05ba..2a5464e 100755 --- a/nur_bin/sqlite-storage.php +++ b/nur_bin/sqlite-storage.php @@ -19,16 +19,21 @@ Application::run(new class extends Application { ["-f", "--dbfile", "args" => 1, "help" => "chemin vers la base de données", ], - ["-c", "--channel", "args" => 1, + ["-t", "--table-name", "args" => 1, "help" => "nom de la table porteuse du canal de données", ], + ["-c", "--channel-class", "args" => 1, + "help" => "nom de la classe dérivée de CapacitorChannel", + ], ]; - protected $dbfile; + protected ?string $dbfile = null; - protected $channel; + protected ?string $tableName = null; - protected $args; + protected ?string $channelClass = null; + + protected ?array $args = null; protected static function isa_cond(string $arg, ?array &$ms=null): bool { return preg_match('/^(.+?)\s*(=|<>|<|>|<=|>=|(?:is\s+)?null|(?:is\s+)?not\s+null)\s*(.*)$/', $arg, $ms); @@ -39,15 +44,21 @@ Application::run(new class extends Application { if ($dbfile === null) self::die("Vous devez spécifier la base de données"); if (!file_exists($dbfile)) self::die("$dbfile: fichier introuvable"); - if ($this->channel === null) self::die("Vous devez spécifier le canal de données"); + if ($this->channelClass !== null) { + $channelClass = str_replace("/", "\\", $this->channelClass); + $channel = new $channelClass; + } elseif ($this->tableName !== null) { + $channel = new class($this->tableName) extends CapacitorChannel { + public function __construct(?string $name=null) { + parent::__construct($name); + $this->tableName = $name; + } + }; + } else { + self::die("Vous devez spécifier le canal de données"); + } $storage = new SqliteStorage($dbfile); - $channel = new class($this->channel) extends CapacitorChannel { - public function __construct(?string $name=null) { - parent::__construct($name); - $this->tableName = $name; - } - }; $capacitor = new Capacitor($storage, $channel); $args = $this->args; diff --git a/src/db/Capacitor.php b/src/db/Capacitor.php index 8d890b7..79b11dc 100644 --- a/src/db/Capacitor.php +++ b/src/db/Capacitor.php @@ -2,7 +2,7 @@ namespace nur\sery\db; /** - * Class Capacitor: un objet permettant d'attaquer un canal spécique d'une + * Class Capacitor: un objet permettant d'attaquer un canal spécifique d'une * instance de {@link CapacitorStorage} */ class Capacitor { @@ -15,9 +15,17 @@ class Capacitor { /** @var CapacitorStorage */ protected $storage; + function getStorage(): CapacitorStorage { + return $this->storage; + } + /** @var CapacitorChannel */ protected $channel; + function getChannel(): CapacitorChannel { + return $this->channel; + } + function exists(): bool { return $this->storage->_exists($this->channel); } @@ -30,11 +38,11 @@ class Capacitor { $this->storage->_reset($this->channel); } - function charge($item, ?callable $func=null, ?array $args=null): int { + function charge($item, $func=null, ?array $args=null): int { return $this->storage->_charge($this->channel, $item, $func, $args); } - function discharge($filter=null, ?bool $reset=null): iterable { + function discharge(bool $reset=true): iterable { return $this->storage->_discharge($this->channel, $reset); } @@ -50,7 +58,7 @@ class Capacitor { return $this->storage->_all($this->channel, $filter); } - function each($filter, ?callable $func=null, ?array $args=null): int { + function each($filter, $func=null, ?array $args=null): int { return $this->storage->_each($this->channel, $filter, $func, $args); } diff --git a/src/db/CapacitorChannel.php b/src/db/CapacitorChannel.php index f113858..c4c342b 100644 --- a/src/db/CapacitorChannel.php +++ b/src/db/CapacitorChannel.php @@ -115,7 +115,7 @@ class CapacitorChannel { * chargement de $item * * Cette méthode est utilisée par {@link Capacitor::charge()}. Si la clé - * primaire est retournée (il s'agit généralement de "id_"), la ligne + * primaire est incluse (il s'agit généralement de "id_"), la ligne * correspondate est mise à jour si elle existe. */ function getItemValues($item): ?array { @@ -137,36 +137,46 @@ class CapacitorChannel { * créer un nouvel élément * * @param mixed $item l'élément à charger - * @param array $updates les valeurs calculées par {@link getItemValues()} - * @return ?array le cas échéant, un tableau non null à merger dans $updates + * @param array $rowValues la ligne à créer, calculée à partir de $item et des + * valeurs retournées par {@link getItemValues()} + * @return ?array le cas échéant, un tableau non null à merger dans $rowValues * et utilisé pour provisionner la ligne nouvellement créée * * Si $item est modifié dans cette méthode, il est possible de le retourner * avec la clé "item" pour mettre à jour la ligne correspondante. */ - function onCreate($item, array $updates): ?array { + function onCreate($item, array $rowValues): ?array { return null; } + /** + * retourne true si un nouvel élément ou un élément mis à jour a été chargé. + * false si l'élément chargé est identique au précédent. + * + * cette méthode doit être utilisée dans {@link self::onUpdate()} + */ + function wasModified(array $rowValues): bool { + return array_key_exists("modified_", $rowValues); + } + /** * méthode appelée lors du chargement avec {@link Capacitor::charge()} pour * mettre à jour un élément existant * * @param mixed $item l'élément à charger - * @param array $updates les valeurs calculées par {@link getItemValues()} - * Si l'élément a été modifié par rapport au dernier chargement, ce tableau - * contient une clé "modified_" avec la date de modification. - * @param array $row la ligne à mettre à jour. - * @return ?array le cas échéant, un tableau non null à merger dans $updates - * et utilisé pour mettre à jour la ligne existante + * @param array $rowValues la nouvelle ligne, calculée à partir de $item et + * des valeurs retournées par {@link getItemValues()} + * @param array $prowValues la précédente ligne, chargée depuis la base de + * données + * @return ?array null s'il ne faut pas mettre à jour la ligne. sinon, ce + * tableau est mergé dans $values puis utilisé pour mettre à jour la ligne + * existante * - * Si $item est modifié dans cette méthode, il est possible de le retourner - * avec la clé "item" pour mettre à jour la ligne correspondante. - * - * La clé primaire (il s'agit généralement de "id_") ne peut pas être + * - Il est possible de mettre à jour $item en le retourant avec la clé "item" + * - La clé primaire (il s'agit généralement de "id_") ne peut pas être * modifiée. si elle est retournée, elle est ignorée */ - function onUpdate($item, array $updates, array $row): ?array { + function onUpdate($item, array $rowValues, array $prowValues): ?array { return null; } @@ -175,16 +185,15 @@ class CapacitorChannel { * {@link Capacitor::each()} * * @param mixed $item l'élément courant - * @param ?array $row la ligne à mettre à jour. + * @param ?array $rowValues la ligne courante * @return ?array le cas échéant, un tableau non null utilisé pour mettre à * jour la ligne courante * - * Si $item est modifié dans cette méthode, il est possible de le retourner - * avec la clé "item" pour mettre à jour la ligne correspondante - * La colonne "id_" ne peut pas être modifiée: si "id_" est retourné, il est - * ignoré + * - Il est possible de mettre à jour $item en le retourant avec la clé "item" + * - La clé primaire (il s'agit généralement de "id_") ne peut pas être + * modifiée. si elle est retournée, elle est ignorée */ - function onEach($item, array $row): ?array { + function onEach($item, array $rowValues): ?array { return null; } } diff --git a/src/db/CapacitorStorage.php b/src/db/CapacitorStorage.php index 999f1b3..d345cc4 100644 --- a/src/db/CapacitorStorage.php +++ b/src/db/CapacitorStorage.php @@ -27,9 +27,8 @@ abstract class CapacitorStorage { return $channel; } - const PRIMARY_KEY_DEFINITION = [ - "id_" => "integer primary key autoincrement", - ]; + /** DOIT être défini dans les classes dérivées */ + const PRIMARY_KEY_DEFINITION = null; const COLUMN_DEFINITIONS = [ "item__" => "text", @@ -41,9 +40,9 @@ abstract class CapacitorStorage { protected function ColumnDefinitions(CapacitorChannel $channel): array { $definitions = []; if ($channel->getPrimaryKeys() === null) { - $definitions[] = self::PRIMARY_KEY_DEFINITION; + $definitions[] = static::PRIMARY_KEY_DEFINITION; } - $definitions[] = self::COLUMN_DEFINITIONS; + $definitions[] = static::COLUMN_DEFINITIONS; $definitions[] = $channel->getColumnDefinitions(); return cl::merge(...$definitions); } @@ -130,7 +129,7 @@ abstract class CapacitorStorage { $this->_reset($this->getChannel($channel)); } - abstract function _charge(CapacitorChannel $channel, $item, ?callable $func, ?array $args): int; + abstract function _charge(CapacitorChannel $channel, $item, $func, ?array $args): int; /** * charger une valeur dans le canal @@ -144,7 +143,7 @@ abstract class CapacitorStorage { * @return int 1 si l'objet a été chargé ou mis à jour, 0 s'il existait * déjà à l'identique dans le canal */ - function charge(?string $channel, $item, ?callable $func=null, ?array $args=null): int { + function charge(?string $channel, $item, $func=null, ?array $args=null): int { return $this->_charge($this->getChannel($channel), $item, $func, $args); } @@ -184,7 +183,7 @@ abstract class CapacitorStorage { return $this->_all($this->getChannel($channel), $filter); } - abstract function _each(CapacitorChannel $channel, $filter, ?callable $func, ?array $args): int; + abstract function _each(CapacitorChannel $channel, $filter, $func, ?array $args): int; /** * appeler une fonction pour chaque élément du canal spécifié. @@ -196,7 +195,7 @@ abstract class CapacitorStorage { * * @return int le nombre de lignes parcourues */ - function each(?string $channel, $filter, ?callable $func=null, ?array $args=null): int { + function each(?string $channel, $filter, $func=null, ?array $args=null): int { return $this->_each($this->getChannel($channel), $filter, $func, $args); } diff --git a/src/db/mysql/MysqlStorage.php b/src/db/mysql/MysqlStorage.php index 2a3fc9b..4088103 100644 --- a/src/db/mysql/MysqlStorage.php +++ b/src/db/mysql/MysqlStorage.php @@ -22,6 +22,10 @@ class MysqlStorage extends CapacitorStorage { return $this->mysql; } + const PRIMARY_KEY_DEFINITION = [ + "id_" => "integer primary key auto_increment", + ]; + protected function _create(CapacitorChannel $channel): void { if (!$channel->isCreated()) { $cols = $this->ColumnDefinitions($channel); @@ -58,8 +62,9 @@ class MysqlStorage extends CapacitorStorage { $channel->setCreated(false); } - function _charge(CapacitorChannel $channel, $item, ?callable $func, ?array $args): int { + function _charge(CapacitorChannel $channel, $item, $func, ?array $args): int { $this->_create($channel); + $tableName = $channel->getTableName(); $now = date("Y-m-d H:i:s"); $item__ = serialize($item); $sum_ = sha1($item__); @@ -73,13 +78,7 @@ class MysqlStorage extends CapacitorStorage { # modification $prow = $this->mysql->one([ "select", - "cols" => array_merge($primaryKeys, [ - "item__", - "sum_", - "created_", - "modified_", - ]), - "from" => $channel->getTableName(), + "from" => $tableName, "where" => $rowIds, ]); } @@ -98,10 +97,12 @@ class MysqlStorage extends CapacitorStorage { $args = [$item, $values, ...$args]; } else { # modification - $row = cl::merge($row, [ - "modified_" => $now, - ]); - if ($sum_ !== $prow["sum_"]) $insert = false; + if ($sum_ !== $prow["sum_"]) { + $insert = false; + $row = cl::merge($row, [ + "modified_" => $now, + ]); + } if ($func === null) $func = "->onUpdate"; func::ensure_func($func, $channel, $args); $values = $this->unserialize($channel, $row); @@ -129,13 +130,13 @@ class MysqlStorage extends CapacitorStorage { } elseif ($insert) { $this->mysql->exec([ "insert", - "into" => $channel->getTableName(), + "into" => $tableName, "values" => $row, ]); } else { $this->mysql->exec([ "update", - "table" => $channel->getTableName(), + "table" => $tableName, "values" => $row, "where" => $rowIds, ]); @@ -195,8 +196,9 @@ class MysqlStorage extends CapacitorStorage { } } - function _each(CapacitorChannel $channel, $filter, ?callable $func, ?array $args): int { - if ($func === null) $func = [$channel, "onEach"]; + function _each(CapacitorChannel $channel, $filter, $func, ?array $args): int { + if ($func === null) $func = "->onEach"; + func::ensure_func($func, $channel, $args); $onEach = func::_prepare($func); $mysql = $this->mysql; $tableName = $channel->getTableName(); diff --git a/src/db/pdo/Pdo.php b/src/db/pdo/Pdo.php index a285fd5..5d752b2 100644 --- a/src/db/pdo/Pdo.php +++ b/src/db/pdo/Pdo.php @@ -19,8 +19,6 @@ class Pdo { "config" => $pdo->config, "migrate" => $pdo->migration, ], $params)); - } elseif (is_array($pdo)) { - return new static(null, cl::merge($pdo, $params)); } else { return new static($pdo, $params); } diff --git a/src/db/sqlite/SqliteStorage.php b/src/db/sqlite/SqliteStorage.php index 65d05b1..705806f 100644 --- a/src/db/sqlite/SqliteStorage.php +++ b/src/db/sqlite/SqliteStorage.php @@ -22,6 +22,10 @@ class SqliteStorage extends CapacitorStorage { return $this->sqlite; } + const PRIMARY_KEY_DEFINITION = [ + "id_" => "integer primary key autoincrement", + ]; + protected function _create(CapacitorChannel $channel): void { if (!$channel->isCreated()) { $cols = $this->ColumnDefinitions($channel); @@ -56,8 +60,9 @@ class SqliteStorage extends CapacitorStorage { $channel->setCreated(false); } - function _charge(CapacitorChannel $channel, $item, ?callable $func, ?array $args): int { + function _charge(CapacitorChannel $channel, $item, $func, ?array $args): int { $this->_create($channel); + $tableName = $channel->getTableName(); $now = date("Y-m-d H:i:s"); $item__ = serialize($item); $sum_ = sha1($item__); @@ -71,13 +76,7 @@ class SqliteStorage extends CapacitorStorage { # modification $prow = $this->sqlite->one([ "select", - "cols" => array_merge($primaryKeys, [ - "item__", - "sum_", - "created_", - "modified_", - ]), - "from" => $channel->getTableName(), + "from" => $tableName, "where" => $rowIds, ]); } @@ -96,10 +95,12 @@ class SqliteStorage extends CapacitorStorage { $args = [$item, $values, ...$args]; } else { # modification - $row = cl::merge($row, [ - "modified_" => $now, - ]); - if ($sum_ !== $prow["sum_"]) $insert = false; + if ($sum_ !== $prow["sum_"]) { + $insert = false; + $row = cl::merge($row, [ + "modified_" => $now, + ]); + } if ($func === null) $func = "->onUpdate"; func::ensure_func($func, $channel, $args); $values = $this->unserialize($channel, $row); @@ -127,13 +128,13 @@ class SqliteStorage extends CapacitorStorage { } elseif ($insert) { $this->sqlite->exec([ "insert", - "into" => $channel->getTableName(), + "into" => $tableName, "values" => $row, ]); } else { $this->sqlite->exec([ "update", - "table" => $channel->getTableName(), + "table" => $tableName, "values" => $row, "where" => $rowIds, ]); @@ -193,8 +194,9 @@ class SqliteStorage extends CapacitorStorage { } } - function _each(CapacitorChannel $channel, $filter, ?callable $func, ?array $args): int { - if ($func === null) $func = [$channel, "onEach"]; + function _each(CapacitorChannel $channel, $filter, $func, ?array $args): int { + if ($func === null) $func = "->onEach"; + func::ensure_func($func, $channel, $args); $onEach = func::_prepare($func); $sqlite = $this->sqlite; $tableName = $channel->getTableName(); diff --git a/tests/db/sqlite/SqliteStorageTest.php b/tests/db/sqlite/SqliteStorageTest.php index df3a91e..33fdac1 100644 --- a/tests/db/sqlite/SqliteStorageTest.php +++ b/tests/db/sqlite/SqliteStorageTest.php @@ -77,7 +77,7 @@ class SqliteStorageTest extends TestCase { }; $capacitor->each(["age" => [">", 10]], $setDone, ["++"]); $capacitor->each(["done" => 0], $setDone, null); - Txx(cl::all($capacitor->discharge(null, false))); + Txx(cl::all($capacitor->discharge(false))); $capacitor->close(); self::assertTrue(true);