From b5e8a642ba5d922c4a0e6f8b6bc2d1f3a07d9a41 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laurent=20L=C3=A9cluse?= <laurent.lecluse@unicaen.fr>
Date: Mon, 26 Aug 2024 13:56:17 +0200
Subject: [PATCH] =?UTF-8?q?-=20[Fix]=20Test=20de=20comparaison=20des=20flo?=
 =?UTF-8?q?at=20&=20doubles=20tenant=20compte=20des=20probl=C3=A9matiques?=
 =?UTF-8?q?=20d'arrondis=20de=20PHP=20-=20Dans=20le=20Table->merge,=20poss?=
 =?UTF-8?q?ibilit=C3=A9=20de=20d=C3=A9finir=20une=20requ=C3=AAte=20personn?=
 =?UTF-8?q?alis=C3=A9e=20pour=20les=20cas=20complexes?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CHANGELOG.md  |   7 +++
 src/Table.php | 115 +++++++++++++++++++++++++++++---------------------
 2 files changed, 75 insertions(+), 47 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e0193a2..dff6d80 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+branche modernisation-gestion-donnees
+-------------------------------------
+
+- [Fix] Test de comparaison des float & doubles tenant compte des problématiques d'arrondis de PHP
+- Dans le Table->merge, possibilité de définir une requête personnalisée pour les cas complexes
+
+
 0.9.8 (22/08/2024)
 ------------------
 
diff --git a/src/Table.php b/src/Table.php
index 6e86dcf..8599da0 100644
--- a/src/Table.php
+++ b/src/Table.php
@@ -3,7 +3,6 @@
 namespace Unicaen\BddAdmin;
 
 
-
 class Table
 {
 
@@ -40,11 +39,11 @@ class Table
     public function getDdl(): array
     {
         if (empty($this->ddl)) {
-            $sTable    = $this->getBdd()->table();
+            $sTable = $this->getBdd()->table();
             $this->ddl = $sTable->get($this->name)[$this->name];
         }
-        if (!$this->ddl){
-            throw new \Exception('La DDL de la table '.$this->name.' n\'a pas pu être calculée');
+        if (!$this->ddl) {
+            throw new \Exception('La DDL de la table ' . $this->name . ' n\'a pas pu être calculée');
         }
 
         return $this->ddl;
@@ -63,7 +62,7 @@ class Table
 
     public function hasHistorique(): bool
     {
-        $ddl      = $this->getDdl();
+        $ddl = $this->getDdl();
         $hasHisto = isset($ddl['columns']['HISTO_CREATION'])
             && isset($ddl['columns']['HISTO_MODIFICATION'])
             && isset($ddl['columns']['HISTO_DESTRUCTION'])
@@ -78,7 +77,7 @@ class Table
 
     public function hasImport(): bool
     {
-        $ddl       = $this->getDdl();
+        $ddl = $this->getDdl();
         $hasImport = isset($ddl['columns']['SOURCE_ID'])
             && isset($ddl['columns']['SOURCE_CODE']);
 
@@ -101,16 +100,17 @@ class Table
 
 
 
-    public function select( array|int|null $where = null, array $options = []): array|null|SelectParser
+    public function select(array|int|null $where = null, array $options = []): array|null|SelectParser
     {
         /* Initialisation des entrées */
         $defaultOptions = [
-            'fetch'   => Bdd::FETCH_ALL,
-            'types'   => $this->makeTypesOptions(),
-            'key'     => null,
-            'orderBy' => '',
+            'custom-select' => null,
+            'fetch'         => Bdd::FETCH_ALL,
+            'types'         => $this->makeTypesOptions(),
+            'key'           => null,
+            'orderBy'       => '',
         ];
-        $options        = array_merge($defaultOptions, $options);
+        $options = array_merge($defaultOptions, $options);
 
         $ddl = $this->getDdl();
 
@@ -120,9 +120,14 @@ class Table
             if ($cols != '') $cols .= ', ';
             $cols .= $colDdl['name'] ?? $cname;
         }
-        $sql    = "SELECT $cols FROM ".$this->name;
+        $sql = "SELECT $cols FROM ";
+        if ($options['custom-select']) {
+            $sql .= "(" . $options['custom-select'] . ") t";
+        } else {
+            $sql .= $this->name;
+        }
         $params = [];
-        $sql    .= $this->makeWhere($where, $options, $params);
+        $sql .= $this->makeWhere($where, $options, $params);
 
         if ($options['orderBy']) {
             $sql .= ' ORDER BY ' . $options['orderBy'];
@@ -133,7 +138,7 @@ class Table
             /* Mise en forme des résultats */
             $data = [];
             foreach ($select as $d) {
-                $keyValue        = $this->makeKey($d, $options['key']);
+                $keyValue = $this->makeKey($d, $options['key']);
                 $data[$keyValue] = $d;
             }
 
@@ -150,7 +155,7 @@ class Table
         $options = ['types' => $this->makeTypesOptions(), 'fetch' => Bdd::FETCH_EACH];
 
         $count = (int)$source->select('SELECT count(*) C FROM ' . $this->getName(), [], ['fetch' => Bdd::FETCH_ONE])['C'];
-        $r     = $source->select('SELECT * FROM ' . $this->getName(), [], $options);
+        $r = $source->select('SELECT * FROM ' . $this->getName(), [], $options);
 
         if (!$this->getBdd()->isInCopy()) {
             $this->getBdd()->logBegin("Copie de la table " . $this->getName());
@@ -189,7 +194,7 @@ class Table
         $options = ['types' => $this->makeTypesOptions(), 'fetch' => Bdd::FETCH_EACH];
 
         $count = (int)$this->getBdd()->select('SELECT count(*) C FROM ' . $this->getName(), [], ['fetch' => Bdd::FETCH_ONE])['C'];
-        $r     = $this->getBdd()->select('SELECT * FROM ' . $this->getName(), [], $options);
+        $r = $this->getBdd()->select('SELECT * FROM ' . $this->getName(), [], $options);
 
         if (!$this->getBdd()->isInCopy()) {
             $this->getBdd()->logBegin("Sauvegarde de la table " . $this->getName());
@@ -237,10 +242,10 @@ class Table
             $this->getBdd()->logBegin("Restauration de la table " . $this->getName());
         }
 
-        $buff    = fopen($filename, 'r');
-        $count   = fgets($buff);
-        $count   = (int)trim($count);
-        $data    = '';
+        $buff = fopen($filename, 'r');
+        $count = fgets($buff);
+        $count = (int)trim($count);
+        $data = '';
         $current = 0;
         $this->getBdd()->beginTransaction();
         while (($d = fgets($buff)) !== false) {
@@ -305,8 +310,8 @@ class Table
             if (!isset($data['SOURCE_ID'])) $data['SOURCE_ID'] = $sourceId;
         }
 
-        $cols   = [];
-        $vals   = [];
+        $cols = [];
+        $vals = [];
         $params = [];
         foreach ($data as $col => $val) {
             $transformer = isset($options['columns'][$col]['transformer']) ? $options['columns'][$col]['transformer'] : null;
@@ -322,7 +327,7 @@ class Table
 
         $cols = implode(", ", $cols);
         $vals = implode(", ", $vals);
-        $sql  = "INSERT INTO ".$this->name." ($cols) VALUES ($vals)";
+        $sql = "INSERT INTO " . $this->name . " ($cols) VALUES ($vals)";
 
         return $bdd->exec($sql, $params, $this->makeTypesOptions());
     }
@@ -349,11 +354,11 @@ class Table
             if (isset($options['columns'][$col]['transformer'])) {
                 $transVal = '(' . sprintf($options['columns'][$col]['transformer'], $transVal) . ')';
             }
-            $dataSql               .= $col . '=' . $transVal;
+            $dataSql .= $col . '=' . $transVal;
             $params['new_' . $col] = $val;
         }
 
-        $sql = "UPDATE ".$this->name." SET $dataSql" . $this->makeWhere($where, $options, $params);
+        $sql = "UPDATE " . $this->name . " SET $dataSql" . $this->makeWhere($where, $options, $params);
 
         return $bdd->exec($sql, $params, $this->makeTypesOptions());
     }
@@ -363,7 +368,7 @@ class Table
     public function delete(int|string|array|null $where = null, array $options = []): bool
     {
         $params = [];
-        $sql    = "DELETE FROM ".$this->name . $this->makeWhere($where, $options, $params);
+        $sql = "DELETE FROM " . $this->name . $this->makeWhere($where, $options, $params);
 
         return $this->getBdd()->exec($sql, $params);
     }
@@ -372,7 +377,7 @@ class Table
 
     public function truncate(): bool
     {
-        $sql = "TRUNCATE TABLE ".$this->name;
+        $sql = "TRUNCATE TABLE " . $this->name;
 
         return $this->getBdd()->exec($sql);
     }
@@ -385,20 +390,21 @@ class Table
 
         /* Initialisation */
         $defaultOptions = [
+            'custom-select'      => null,
             'where'              => null,
             'key'                => $key,
             'delete'             => true,
             'soft-delete'        => false,
             'insert'             => true,
             'update'             => true,
-            'return-insert-data'  => false,
+            'return-insert-data' => false,
             'update-cols'        => [],
             'update-ignore-cols' => [],
             'update-only-null'   => [],
         ];
-        $options        = array_merge($defaultOptions, $options);
+        $options = array_merge($defaultOptions, $options);
 
-        if ($options['return-insert-data']){
+        if ($options['return-insert-data']) {
             $result['insert-data'] = [];
         }
 
@@ -408,7 +414,7 @@ class Table
         $histoUserId = (int)$bdd->getOption('histo-user-id');
         $hasHistorique = $this->hasHistorique();
 
-        if (empty($options['where']) && $hasHistorique){
+        if (empty($options['where']) && $hasHistorique) {
             $options['where'] = 'HISTO_DESTRUCTION IS NULL';
         }
 
@@ -441,18 +447,18 @@ class Table
         while ($o = $stmt->next()) {
             // récupération de k et n
             $k = $this->makeKey($o, $key);
-            if (array_key_exists($k, $new)){
+            if (array_key_exists($k, $new)) {
                 $n = $new[$k];
                 unset($new[$k]);
-            }else{
+            } else {
                 $n = null;
             }
 
             if (empty($n) && $options['soft-delete'] && $hasHistorique && $histoUserId) { // SOFT DELETE
                 //On ne delete pas mais on historise
-                $n                         = $o;
+                $n = $o;
                 $n['HISTO_DESTRUCTEUR_ID'] = $histoUserId;
-                $n['HISTO_DESTRUCTION']    = new \DateTime();
+                $n['HISTO_DESTRUCTION'] = new \DateTime();
                 $this->update($n, $this->makeKeyArray($o, $k));
                 $result['soft-delete']++;
             } elseif (empty($n) && !$options['soft-delete']) { // DELETE
@@ -475,7 +481,14 @@ class Table
                         if (in_array($c, $options['update-only-null']) && $oldc !== null) $ok = false;
 
                         if ($ok) {
-                            $toUpdate[$c] = $n[$c];
+                            if ((is_float($newc) || is_double($newc))
+                                && (is_float($oldc) || is_double($oldc))
+                                && abs($newc - $oldc) > 0.0000000001
+                            ) {
+                                // nécessaire à cause des pb de précision des float/doubles en PHP :
+                                // dans ce cas, on considère quand même que les 2 chiffres sont identiques
+                                $toUpdate[$c] = $n[$c];
+                            }
                         }
                     }
                 }
@@ -487,12 +500,20 @@ class Table
         }
 
         /* Pour finir, insertion de tous les nouveaux éléments */
-        foreach( $new as $k => $n ){
+        foreach ($new as $k => $n) {
             if ($options['insert']) {
                 $this->insert($n);
                 $result['insert']++;
-                if ($options['return-insert-data']){
-                    $result['insert-data'][] = $n;
+                if ($options['return-insert-data']) {
+                    $insertedData = [];
+                    if (isset($n['ID'])) {
+                        $insertedData['ID'] = $n['ID'];
+                    }
+                    foreach ($key as $null => $kc) {
+                        $insertedData[$kc] = $n[$kc];
+                    }
+
+                    $result['insert-data'][$k] = $insertedData;
                 }
             }
         }
@@ -504,7 +525,7 @@ class Table
 
 
 
-    private function makeKeyArray(array $data, $key): array
+    private function makeKeyArray(array $data, array|string|null $key = null): array
     {
         if (!$key && $this->hasId()) {
             $key = 'ID';
@@ -521,7 +542,7 @@ class Table
 
 
 
-    private function makeKey(array $data, $key): string
+    public function makeKey(array $data, array|string|null $key): string
     {
         $keyArray = $this->makeKeyArray($data, $key);
 
@@ -570,16 +591,16 @@ class Table
 
 
                 if (isset($options['columns'][$c]['transformer'])) {
-                    $transVal   = ':' . $c;
-                    $transVal   = '(' . sprintf($options['columns'][$c]['transformer'], $transVal) . ')';
-                    $whereSql   .= $c . ' = ' . $transVal;
+                    $transVal = ':' . $c;
+                    $transVal = '(' . sprintf($options['columns'][$c]['transformer'], $transVal) . ')';
+                    $whereSql .= $c . ' = ' . $transVal;
                     $params[$c] = $v;
                 } else {
                     if ($v === null) {
                         $whereSql .= $c . ' IS NULL';
                     } else {
-                        $transVal   = ':' . $c;
-                        $whereSql   .= $c . ' = ' . $transVal;
+                        $transVal = ':' . $c;
+                        $whereSql .= $c . ' = ' . $transVal;
                         $params[$c] = $v;
                     }
                 }
-- 
GitLab