nur-sery/src/db/CapacitorChannel.php

313 lines
9.8 KiB
PHP

<?php
namespace nur\sery\db;
use nur\sery\cl;
use nur\sery\str;
/**
* Class CapacitorChannel: un canal d'une instance de {@link ICapacitor}
*/
class CapacitorChannel {
const NAME = null;
const TABLE_NAME = null;
const COLUMN_DEFINITIONS = null;
const PRIMARY_KEYS = null;
const MANAGE_TRANSACTIONS = true;
const EACH_COMMIT_THRESHOLD = 100;
static function verifix_name(?string $name): string {
if ($name === null) $name = "default";
return strtolower($name);
}
protected static function verifix_eachCommitThreshold(?int $eachCommitThreshold): ?int {
$eachCommitThreshold ??= static::EACH_COMMIT_THRESHOLD;
if ($eachCommitThreshold < 0) $eachCommitThreshold = null;
return $eachCommitThreshold;
}
function __construct(?string $name=null, ?int $eachCommitThreshold=null, ?bool $manageTransactions=null) {
$this->name = self::verifix_name($name ?? static::NAME);
$this->tableName = static::TABLE_NAME ?? ($this->name."_channel");
$this->manageTransactions = $manageTransactions ?? static::MANAGE_TRANSACTIONS;
$this->eachCommitThreshold = self::verifix_eachCommitThreshold($eachCommitThreshold);
$this->setup = false;
$this->created = false;
$columnDefinitions = cl::withn(static::COLUMN_DEFINITIONS);
$primaryKeys = cl::withn(static::PRIMARY_KEYS);
if ($primaryKeys === null && $columnDefinitions !== null) {
$index = 0;
foreach ($columnDefinitions as $col => $def) {
if ($col === $index) {
$index++;
if (preg_match('/\bprimary\s+key\s+\((.+)\)/i', $def, $ms)) {
$primaryKeys = preg_split('/\s*,\s*/', trim($ms[1]));
}
} else {
if (preg_match('/\bprimary\s+key\b/i', $def)) {
$primaryKeys[] = $col;
}
}
}
}
$this->columnDefinitions = $columnDefinitions;
$this->primaryKeys = $primaryKeys;
}
protected string $name;
function getName(): string {
return $this->name;
}
protected string $tableName;
function getTableName(): string {
return $this->tableName;
}
/**
* @var bool indiquer si les modifications de each doivent être gérées dans
* une transaction. si false, l'utilisateur doit lui même gérer la
* transaction.
*/
protected bool $manageTransactions;
function isManageTransactions(): bool {
return $this->manageTransactions;
}
function setManageTransactions(bool $manageTransactions=true): self {
$this->manageTransactions = $manageTransactions;
return $this;
}
/**
* @var ?int nombre maximum de modifications dans une transaction avant un
* commit automatique dans {@link Capacitor::each()}. Utiliser null pour
* désactiver la fonctionnalité.
*
* ce paramètre n'a d'effet que si $manageTransactions==true
*/
protected ?int $eachCommitThreshold;
function getEachCommitThreshold(): ?int {
return $this->eachCommitThreshold;
}
function setEachCommitThreshold(?int $eachCommitThreshold=null): self {
$this->eachCommitThreshold = self::verifix_eachCommitThreshold($eachCommitThreshold);
return $this;
}
/**
* initialiser ce channel avant sa première utilisation.
*/
protected function setup(): void {
}
protected bool $setup;
function ensureSetup() {
if (!$this->setup) {
$this->setup();
$this->setup = true;
}
}
protected bool $created;
function isCreated(): bool {
return $this->created;
}
function setCreated(bool $created=true): void {
$this->created = $created;
}
protected ?array $columnDefinitions;
/**
* retourner un ensemble de définitions pour des colonnes supplémentaires à
* insérer lors du chargement d'une valeur
*
* la clé primaire "id_" a pour définition "integer primary key autoincrement".
* elle peut être redéfinie, et dans ce cas la valeur à utiliser doit être
* retournée par {@link getItemValues()}
*
* la colonne "item__" contient la valeur sérialisée de l'élément chargé. bien
* que ce soit possible techniquement, cette colonne n'a pas à être redéfinie
*
* les colonnes dont le nom se termine par "_" sont réservées.
* les colonnes dont le nom se termine par "__" sont automatiquement sérialisées
* lors de l'insertion dans la base de données, et automatiquement désérialisées
* avant d'être retournées à l'utilisateur (sans le suffixe "__")
*/
function getColumnDefinitions(): ?array {
return $this->columnDefinitions;
}
protected ?array $primaryKeys;
function getPrimaryKeys(): ?array {
return $this->primaryKeys;
}
/**
* calculer les valeurs des colonnes supplémentaires à insérer pour le
* chargement de $item
*
* Cette méthode est utilisée par {@link Capacitor::charge()}. Si la clé
* primaire est incluse (il s'agit généralement de "id_"), la ligne
* correspondate est mise à jour si elle existe.
*
* Retourner la clé primaire par cette méthode est l'unique moyen de
* déclencher une mise à jour plutôt qu'une nouvelle création.
*/
function getItemValues($item): ?array {
return null;
}
/**
* Avant d'utiliser un id pour rechercher dans la base de donnée, corriger sa
* valeur le cas échéant.
*
* Cette fonction assume que l'unique clé primaire est "id_". Elle n'est pas
* utilisée si une clé primaire multiple est définie.
*/
function verifixId(string &$id): void {
}
/**
* 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 wasRowModified(array $rowValues): bool {
return array_key_exists("modified_", $rowValues);
}
final function serialize($item): ?string {
return $item !== null? serialize($item): null;
}
final function unserialize(?string $serial) {
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;
}
final function isSerialCol(string &$key): bool {
return str::del_suffix($key, "__");
}
final function getSumCols(string $key): array {
return ["${key}__", "${key}__sum_"];
}
function getSum(string $key, $value): array {
$sumCols = $this->getSumCols($key);
$serial = $this->serialize($value);
$sum = $this->sum($serial, $value);
return array_combine($sumCols, [$serial, $sum]);
}
function wasSumModified(string $key, $value, array $prowValues): bool {
$sumCol = $this->getSumCols($key)[1];
$sum = $this->sum(null, $value);
$psum = $prowValues[$sumCol] ?? $this->sum(null, $prowValues[$key] ?? null);
return $sum !== $psum;
}
function _wasSumModified(string $key, array $row, array $prow): bool {
$sumCol = $this->getSumCols($key)[1];
$sum = $row[$sumCol] ?? null;
$psum = $prow[$sumCol] ?? null;
return $sum !== $psum;
}
/**
* méthode appelée lors du chargement avec {@link Capacitor::charge()} pour
* créer un nouvel élément
*
* @param mixed $item l'élément à charger
* @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.
*
* la création ou la mise à jour est uniquement décidée en fonction des
* valeurs calculées par {@link self::getItemValues()}. Bien que cette méthode
* peut techniquement retourner de nouvelles valeurs pour la clé primaire, ça
* risque de créer des doublons
*/
function onCreate($item, array $rowValues, ?array $alwaysNull): ?array {
return null;
}
/**
* 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 $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
*
* - 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 $rowValues, array $prowValues): ?array {
return null;
}
/**
* méthode appelée lors du parcours des éléments avec
* {@link Capacitor::each()}
*
* @param mixed $item l'élément courant
* @param ?array $rowValues la ligne courante
* @return ?array le cas échéant, un tableau non null utilisé pour mettre à
* jour la ligne courante
*
* - 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 $rowValues): ?array {
return null;
}
/**
* méthode appelée lors du parcours des éléments avec
* {@link Capacitor::delete()}
*
* @param mixed $item l'élément courant
* @param ?array $rowValues la ligne courante
* @return bool true s'il faut supprimer la ligne, false sinon
*/
function onDelete($item, array $rowValues): bool {
return true;
}
}