Skip to content
Snippets Groups Projects
Commit 6fac0fb4 authored by Bertrand Gauthier's avatar Bertrand Gauthier
Browse files

- Config : possibilité d'utiliser des noms de colonnes en plus des noms d'attributs Doctrine.

- Suppression des dépendances à unicaen/app en faveur de la nouvelle bibliothèque unicaen/sql.
- Renommage de la table _METADATA en `unicaen_db_anonym`.
- [FIX] Plus d'anonymisation si la valeur est null.
parent 52c6740c
No related branches found
No related tags found
No related merge requests found
Pipeline #28504 passed
CHANGELOG CHANGELOG
========= =========
3.1.0
-----
- Config : possibilité d'utiliser des noms de colonnes en plus des noms d'attributs Doctrine.
- Suppression des dépendances à unicaen/app en faveur de la nouvelle bibliothèque unicaen/sql.
- Renommage de la table _METADATA en `unicaen_db_anonym`.
- [FIX] Plus d'anonymisation si la valeur est null.
3.0.0 3.0.0
----- -----
- PHP 8 requis - PHP 8 requis
......
...@@ -10,17 +10,19 @@ lors de la préparation d'une bdd de démo par exemple). ...@@ -10,17 +10,19 @@ lors de la préparation d'une bdd de démo par exemple).
Les données fictives utilisées pour anonymiser sont générées à l'aide de [FakerPHP](https://github.com/FakerPHP/Faker). Les données fictives utilisées pour anonymiser sont générées à l'aide de [FakerPHP](https://github.com/FakerPHP/Faker).
Les tables et colonnes concernées sont spécifiées dans un fichier de config, en terme de classe d'entité et de champs Les tables et colonnes à traiter sont spécifiées dans un fichier de config, en terme de classes d'entités et d'attributs
Doctrine. Cf. [exemple de config](./config/unicaen-db-anonym.local.php.dist). Doctrine. Cf. [exemple de config](./config/unicaen-db-anonym.local.php.dist).
Il est possibles d'écarter de l'anonymisation/restauration certains enregistrements selon la valeur d'un champ Il est possibles d'écarter certains enregistrements de ces tables selon la valeur d'une colonne
(cf. clés de config `'except'`). (cf. clé de config `'except'`).
Seules les valeurs de colonnes non null sont anonymisées.
Préalables Préalables
---------- ----------
- Une table `_METADATA` munie d'un témoin d'anonymisation est utilisée pour enregistrer le fait que la bdd a été anonymisée - Une table `unicaen_db_anonym` est utilisée pour enregistrer le fait que la bdd a été anonymisée
ou non. *Cela empêche qu'un script de restauration ne soit généré à partir d'une bdd anonymisée, auquel cas on ne pourrait ou non. *Cela empêche qu'un script de restauration ne soit généré à partir d'une bdd anonymisée, auquel cas on ne pourrait
pas restaurer les données d'origine !* pas restaurer les données d'origine !*
Le script de création de cette table dans une bdd Postgres est fourni [ici](./sql/schema.postgres.sql). Le script de création de cette table dans une bdd Postgres est fourni [ici](./sql/schema.postgres.sql).
...@@ -32,12 +34,13 @@ Actions disponibles ...@@ -32,12 +34,13 @@ Actions disponibles
### Génération des scripts d'anonymisation/restauration ### Génération des scripts d'anonymisation/restauration
Le module génére 2 scripts SQL : Le module génére 2 scripts SQL :
- 1 script d'*anonymisation* des données (clé de config `['output']['anonymisation']`) ; - 1 script d'*anonymisation* des données, dont le chemin est spécifié par la clé de config `['output']['anonymisation']`) ;
- 1 script de *restauration* des données originales (clé de config `['output']['restauration']`). - 1 script de *restauration* des données originales, dont le chemin est spécifié par la clé de config `['output']['restauration']`).
L'idée est de parcourir tous les enregistrements de chaque entité/table (sauf ceux écartés d'après la config) pour L'idée est de parcourir tous les enregistrements de chaque entité/table (sauf ceux à écarter) et de
générer d'une part un `update ... where id = ...` d'anonymisation inscrit dans le script d'anonymisation générer pour chacun :
et d'autre part un `update ... where id = ...` de restauration inscrit dans le script de restauration. - d'une part un `update ... set ... where id = ...` d'anonymisation (inscrit dans le script d'anonymisation) ;
- d'autre part un `update ... set ... where id = ...` de restauration (inscrit dans le script de restauration).
Aperçu d'un script d'anonymisation : Aperçu d'un script d'anonymisation :
```sql ```sql
...@@ -59,33 +62,37 @@ update DOCTORANT set INE = '03140E00N22' where id = 30071 ; ...@@ -59,33 +62,37 @@ update DOCTORANT set INE = '03140E00N22' where id = 30071 ;
update DOCTORANT set INE = '03140E00N33' where id = 30073 ; update DOCTORANT set INE = '03140E00N33' where id = 30073 ;
``` ```
Commande pour lancer la génération :
```bash ```bash
php public/index.php unicaen-db-anonym generer php public/index.php unicaen-db-anonym generer
``` ```
### Lancement de l'anonymisation ### Lancement de l'anonymisation
Si le script d'anonymisation **et** celui de restauration ont été générés, le module est en mesure d'exécuter le script Le module est en mesure d'exécuter le script d'anonymisation à condition que :
d'anonymisation. - le témoin d'anonymisation est à '0' dans la table `unicaen_db_anonym`,
- le script d'anonymisation **et** celui de restauration ont été générés.
```bash ```bash
php public/index.php unicaen-db-anonym anonymiser php public/index.php unicaen-db-anonym anonymiser
``` ```
À l'issue de l'anonymisation, le témoin d'anonymisation est mis à '1' dans la table `_METADATA`, ce qui empêchera de À l'issue de l'anonymisation, le témoin d'anonymisation est mis à '1' dans la table `unicaen_db_anonym`, ce qui empêchera de
lancer inutilement une nouvelle anonymisation mais surtout de regénérer un script de restauration à partir de la bdd lancer inutilement une nouvelle anonymisation mais surtout de regénérer un script de restauration à partir de la bdd
anonymisée. anonymisée.
### Lancement de la restauration ### Lancement de la restauration
Si le script d'anonymisation **et** celui de restauration ont été générés, le module est en mesure d'exécuter le script Le module est en mesure d'exécuter le script de restauration à condition que :
de restauration. - le témoin d'anonymisation est à '1' dans la table `unicaen_db_anonym`,
- le script d'anonymisation **et** celui de restauration ont été générés.
```bash ```bash
php public/index.php unicaen-db-anonym anonymiser php public/index.php unicaen-db-anonym restaurer
``` ```
À l'issue de la restauration, le témoin d'anonymisation est mis à '0' dans la table `_METADATA`, ce qui empêchera de À l'issue de la restauration, le témoin d'anonymisation est mis à '0' dans la table `unicaen_db_anonym`, ce qui empêchera de
lancer inutilement une nouvelle restauration. lancer inutilement une nouvelle restauration.
...@@ -93,6 +100,6 @@ Remarques importantes ...@@ -93,6 +100,6 @@ Remarques importantes
--------------------- ---------------------
- Les scripts d'anonymisation/restauration ne visent que les enregistrements qui existaient dans la bdd cible au moment - Les scripts d'anonymisation/restauration ne visent que les enregistrements qui existaient dans la bdd cible au moment
de leur génération. Si les données de cette bdd évoluent dans le temps (par exemple à l'issue d'un import de données de leur génération. Donc si les données de cette bdd évoluent dans le temps (par exemple à l'issue d'un import de données
périodique ou au fil de l'utilisation de l'appli pointant sur cette bdd), ces scripts n'impacteront pas les nouveaux périodique ou au fil de l'utilisation de l'appli), ces scripts n'impacteront pas les nouveaux
enregistrements apparus (au mieux) ou planteront sur les enregistrements disparus (au pire). enregistrements apparus ou planteront sur les enregistrements disparus.
...@@ -9,7 +9,8 @@ ...@@ -9,7 +9,8 @@
], ],
"require": { "require": {
"php": "^8.0", "php": "^8.0",
"unicaen/app": "^6.0", "doctrine/orm": "^3.2",
"unicaen/console": "^6.0",
"webmozart/assert": "^1.11", "webmozart/assert": "^1.11",
"fakerphp/faker": "^1.20" "fakerphp/faker": "^1.20"
}, },
...@@ -26,10 +27,5 @@ ...@@ -26,10 +27,5 @@
"classmap": [ "classmap": [
"./Module.php" "./Module.php"
] ]
},
"config": {
"allow-plugins": {
"laminas/laminas-dependency-plugin": true
}
} }
} }
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
namespace UnicaenDbAnonym; namespace UnicaenDbAnonym;
use UnicaenAuth\Guard\PrivilegeController;
use UnicaenDbAnonym\Controller\ConsoleController; use UnicaenDbAnonym\Controller\ConsoleController;
use UnicaenDbAnonym\Controller\ConsoleControllerFactory; use UnicaenDbAnonym\Controller\ConsoleControllerFactory;
use UnicaenDbAnonym\Controller\IndexController; use UnicaenDbAnonym\Controller\IndexController;
...@@ -13,6 +12,13 @@ use UnicaenDbAnonym\Service\DbService; ...@@ -13,6 +12,13 @@ use UnicaenDbAnonym\Service\DbService;
use UnicaenDbAnonym\Service\DbServiceFactory; use UnicaenDbAnonym\Service\DbServiceFactory;
return [ return [
'unicaen-db-anonym' => [
'output' => [
'anonymisation' => '/tmp/unicaen_db_anonym_anonymisation.sql',
'restauration' => '/tmp/unicaen_db_anonym_restauration.sql',
],
'entities' => [],
],
'router' => [ 'router' => [
'routes' => [ 'routes' => [
'unicaen-db-anonym' => [ 'unicaen-db-anonym' => [
...@@ -113,7 +119,7 @@ return [ ...@@ -113,7 +119,7 @@ return [
], ],
'bjyauthorize' => [ 'bjyauthorize' => [
'guards' => [ 'guards' => [
PrivilegeController::class => [ 'UnicaenAuth\Guard\PrivilegeController' => [
[ [
/** /**
* @see ConsoleController::genererAction() * @see ConsoleController::genererAction()
......
-- --
-- Création si nécessaire de la table des métadonnées de la bdd. -- Création si nécessaire de la table des métadonnées de la bdd.
-- --
create table if not exists _METADATA ( create table if not exists unicaen_db_anonym (
key varchar(64) not null, key varchar(64) not null,
value text, value text,
extra text, extra text,
description varchar(128) description varchar(128)
); );
-- Création du témoin d'anonymisation -- Création du témoin d'anonymisation
insert into _METADATA (key, value, description) insert into unicaen_db_anonym (key, value, description)
values ('BDD_ANONYMISEE', '0', 'Indique si les données de cette bdd ont été anonymisées (1) ou non (0)'); values ('BDD_ANONYMISEE', '0', 'Indique si les données de cette bdd ont été anonymisées (1) ou non (0)');
...@@ -11,7 +11,7 @@ class ConsoleController extends AbstractConsoleController ...@@ -11,7 +11,7 @@ class ConsoleController extends AbstractConsoleController
{ {
use AnonymServiceAwareTrait; use AnonymServiceAwareTrait;
public function genererAction() public function genererAction(): void
{ {
$start = microtime(true); $start = microtime(true);
...@@ -21,9 +21,9 @@ class ConsoleController extends AbstractConsoleController ...@@ -21,9 +21,9 @@ class ConsoleController extends AbstractConsoleController
$restaurationScriptPath = $this->anonymService->getRestaurationScriptPath(); $restaurationScriptPath = $this->anonymService->getRestaurationScriptPath();
try { try {
$gen = $this->anonymService->generer(); $gen = $this->anonymService->generer();
foreach ($gen as ['table' => $table, 'fields' => $fields, 'count' => $count]) { foreach ($gen as ['entity' => $entity, 'fields' => $fields, 'count' => $count]) {
$this->console->writeLine( $this->console->writeLine(
sprintf("- Table %s (colonnes %s) : %d lignes", $table, implode(', ', $fields), $count) sprintf("- Entite %s (%s) : %d lignes traitees", $entity, implode(', ', $fields), $count)
); );
} }
} catch (Exception $e) { } catch (Exception $e) {
...@@ -32,12 +32,12 @@ class ConsoleController extends AbstractConsoleController ...@@ -32,12 +32,12 @@ class ConsoleController extends AbstractConsoleController
$end = microtime(true); $end = microtime(true);
$this->console->writeLine("> Script d'anonymisation : " . realpath($anonymisationScriptPath)); $this->console->writeLine(sprintf("> Script d'anonymisation cree : %s", realpath($anonymisationScriptPath)));
$this->console->writeLine("> Script de restauration : " . realpath($restaurationScriptPath)); $this->console->writeLine(sprintf("> Script de restauration cree : %s", realpath($restaurationScriptPath)));
$this->console->writeLine(sprintf("Duree : %.2f s", $end - $start)); $this->console->writeLine(sprintf("Duree : %.2f s", $end - $start));
} }
public function anonymiserAction() public function anonymiserAction(): void
{ {
$scriptPath = $this->anonymService->getAnonymisationScriptPath(); $scriptPath = $this->anonymService->getAnonymisationScriptPath();
...@@ -56,7 +56,7 @@ class ConsoleController extends AbstractConsoleController ...@@ -56,7 +56,7 @@ class ConsoleController extends AbstractConsoleController
$this->console->writeLine(sprintf("Duree : %.2f s", $end - $start)); $this->console->writeLine(sprintf("Duree : %.2f s", $end - $start));
} }
public function restaurerAction() public function restaurerAction(): void
{ {
$scriptPath = $this->anonymService->getRestaurationScriptPath(); $scriptPath = $this->anonymService->getRestaurationScriptPath();
......
...@@ -6,7 +6,7 @@ use Application\Controller\AbstractController; ...@@ -6,7 +6,7 @@ use Application\Controller\AbstractController;
use Exception; use Exception;
use Laminas\View\Model\ViewModel; use Laminas\View\Model\ViewModel;
use RuntimeException; use RuntimeException;
use UnicaenApp\Service\SQL\RunSQLResult; use UnicaenSql\Service\SQL\RunSQLResult;
use UnicaenDbAnonym\Service\AnonymServiceAwareTrait; use UnicaenDbAnonym\Service\AnonymServiceAwareTrait;
class IndexController extends AbstractController class IndexController extends AbstractController
......
...@@ -2,8 +2,9 @@ ...@@ -2,8 +2,9 @@
namespace UnicaenDbAnonym\Service; namespace UnicaenDbAnonym\Service;
use Doctrine\ORM\Mapping\ClassMetadata;
use Generator; use Generator;
use UnicaenApp\Service\SQL\RunSQLResult; use UnicaenSql\Service\SQL\RunSQLResult;
use Webmozart\Assert\Assert; use Webmozart\Assert\Assert;
use Webmozart\Assert\InvalidArgumentException; use Webmozart\Assert\InvalidArgumentException;
...@@ -14,20 +15,9 @@ class AnonymService ...@@ -14,20 +15,9 @@ class AnonymService
{ {
use DbServiceAwareTrait; use DbServiceAwareTrait;
/**
* @var array
*/
protected array $entitiesConfig = []; protected array $entitiesConfig = [];
/**
* @var array
*/
protected array $outputConfig = []; protected array $outputConfig = [];
/**
* @param array $config
* @return self
*/
public function setConfig(array $config): self public function setConfig(array $config): self
{ {
$this->entitiesConfig = $config['entities']; $this->entitiesConfig = $config['entities'];
...@@ -63,7 +53,7 @@ class AnonymService ...@@ -63,7 +53,7 @@ class AnonymService
throw new InvalidArgumentException("Le fichier '$restaurationFilePath' existe déjà !"); throw new InvalidArgumentException("Le fichier '$restaurationFilePath' existe déjà !");
} }
Assert::notEmpty($this->entitiesConfig, "Aucune classe d'entité trouvée dans la config"); Assert::notEmpty($this->entitiesConfig, "Aucune classe d'entité spécifiée dans la config");
$mf = fopen($anonymisationFilePath, 'w'); $mf = fopen($anonymisationFilePath, 'w');
$rf = fopen($restaurationFilePath, 'w'); $rf = fopen($restaurationFilePath, 'w');
...@@ -73,7 +63,7 @@ class AnonymService ...@@ -73,7 +63,7 @@ class AnonymService
Assert::keyExists($entityConfig, $k = 'mapping', "La config de l'entité '$entityName' doit posséder la clé '$k'"); Assert::keyExists($entityConfig, $k = 'mapping', "La config de l'entité '$entityName' doit posséder la clé '$k'");
$metadata = $this->dbService->getEntityClassMetadata($entityName); $metadata = $this->dbService->getEntityClassMetadata($entityName);
$mapping = $entityConfig['mapping']; $mapping = $this->normalizeMapping($metadata, $entityConfig['mapping']);
$except = $entityConfig['except'] ?? []; $except = $entityConfig['except'] ?? [];
$fields = array_keys($mapping); $fields = array_keys($mapping);
...@@ -81,19 +71,24 @@ class AnonymService ...@@ -81,19 +71,24 @@ class AnonymService
$migrateSqlLines = []; $migrateSqlLines = [];
$restaurationSqlLines = []; $restaurationSqlLines = [];
foreach ($records as $record) { foreach ($records as $record) {
$migrateSqlLines[] = $this->dbService->genUpdateForAnonymiser($record, $mapping, $metadata); // on anonymise pas les colonnes dont la valeur est null
$restaurationSqlLines[] = $this->dbService->genUpdateForRestaurer($record, $mapping, $metadata); $preparedRecord = array_filter($record, fn($v) => $v !== null);
$preparedMapping = array_intersect_key($mapping, $preparedRecord);
if (count($preparedMapping) === 0) {
continue;
}
$migrateSqlLines[] = $this->dbService->genUpdateForAnonymiser($preparedRecord, $preparedMapping, $metadata);
$restaurationSqlLines[] = $this->dbService->genUpdateForRestaurer($preparedRecord, $preparedMapping, $metadata);
} }
fputs($mf, implode(PHP_EOL, $migrateSqlLines) . PHP_EOL); fputs($mf, implode(PHP_EOL, $migrateSqlLines) . PHP_EOL);
fputs($rf, implode(PHP_EOL, $restaurationSqlLines) . PHP_EOL); fputs($rf, implode(PHP_EOL, $restaurationSqlLines) . PHP_EOL);
yield [ yield [
'table' => $metadata->getTableName(), 'entity' => $entityName,
'fields' => $fields, 'fields' => $fields,
'count' => count($records), 'count' => count($migrateSqlLines),
]; ];
unset ($records); unset ($records);
...@@ -103,6 +98,17 @@ class AnonymService ...@@ -103,6 +98,17 @@ class AnonymService
fclose($rf); fclose($rf);
} }
protected function normalizeMapping(ClassMetadata $metadata, array $mapping): array
{
// on s'assure d'avoir des noms d'attributs Doctrine comme clés
$fields = array_map(
fn($name) => $metadata->getFieldName($name),
array_keys($mapping)
);
return array_combine($fields, $mapping);
}
/** /**
* @throws \Doctrine\DBAL\ConnectionException * @throws \Doctrine\DBAL\ConnectionException
* @throws \Doctrine\DBAL\Exception * @throws \Doctrine\DBAL\Exception
......
...@@ -14,35 +14,19 @@ use Laminas\Log\Writer\Stream; ...@@ -14,35 +14,19 @@ use Laminas\Log\Writer\Stream;
use Locale; use Locale;
use RuntimeException; use RuntimeException;
use Throwable; use Throwable;
use UnicaenApp\Service\SQL\RunSQLProcess; use UnicaenSql\Service\SQL\RunSQLProcess;
use UnicaenApp\Service\SQL\RunSQLResult; use UnicaenSql\Service\SQL\RunSQLResult;
use Webmozart\Assert\Assert; use Webmozart\Assert\Assert;
class DbService class DbService
{ {
const METADATA_KEY_ANONYMISEE = 'BDD_ANONYMISEE'; const METADATA_KEY_ANONYMISEE = 'BDD_ANONYMISEE';
/**
* @var \Doctrine\ORM\EntityManager
*/
protected EntityManager $entityManager; protected EntityManager $entityManager;
/**
* @var \Doctrine\DBAL\Platforms\AbstractPlatform
*/
protected AbstractPlatform $databasePlatform; protected AbstractPlatform $databasePlatform;
/**
* @var string
*/
protected string $updateSQLTemplate = 'update %s set %s where id = %d ;'; protected string $updateSQLTemplate = 'update %s set %s where id = %d ;';
/**
* @var \Faker\Generator $faker
*/
protected Generator $faker; protected Generator $faker;
public function __construct() public function __construct()
{ {
$this->faker = Factory::create(Locale::getDefault()); $this->faker = Factory::create(Locale::getDefault());
...@@ -60,7 +44,7 @@ class DbService ...@@ -60,7 +44,7 @@ class DbService
public function isBddAnonymisee(): bool public function isBddAnonymisee(): bool
{ {
$sqlTemplate = "select value from _METADATA where key = '%s'"; $sqlTemplate = "select value from unicaen_db_anonym where key = '%s'";
$sql = sprintf($sqlTemplate, self::METADATA_KEY_ANONYMISEE); $sql = sprintf($sqlTemplate, self::METADATA_KEY_ANONYMISEE);
...@@ -82,9 +66,9 @@ class DbService ...@@ -82,9 +66,9 @@ class DbService
return $result === '1'; return $result === '1';
} }
public function setBddAnonymisee(bool $bddAnonymisee) public function setBddAnonymisee(bool $bddAnonymisee): void
{ {
$sqlTemplate = "update _METADATA set value = '%d' where key = '%s'"; $sqlTemplate = "update unicaen_db_anonym set value = '%d' where key = '%s'";
$sql = sprintf($sqlTemplate, $bddAnonymisee ? '1' : '0', self::METADATA_KEY_ANONYMISEE); $sql = sprintf($sqlTemplate, $bddAnonymisee ? '1' : '0', self::METADATA_KEY_ANONYMISEE);
...@@ -114,38 +98,44 @@ class DbService ...@@ -114,38 +98,44 @@ class DbService
return $this->entityManager->getClassMetadata($entityName); return $this->entityManager->getClassMetadata($entityName);
} }
public function genUpdateForAnonymiser(array $record, array $mapping, ClassMetadata $metadata): string public function genUpdateForAnonymiser(array $record, array $mapping, ClassMetadata $metadata): ?string
{ {
$tableName = $metadata->getTableName(); Assert::notEmpty($mapping, "Mapping vide !");
$sets = []; $sets = [];
foreach ($mapping as $name => $fakerConfig) {
foreach ($mapping as $fieldName => $fakerConfig) { $fakeValue = $this->generateFakeValue((array)$fakerConfig, $metadata->getName(), $name);
$fakerConfig = (array)$fakerConfig; $columnName = $metadata->getColumnName($name);
$method = array_shift($fakerConfig); $sets[] = $columnName . ' = ' . $fakeValue;
if ($method === 'null') {
$fakeValue = 'NULL';
} else {
$fakeValue = $this->faker->$method(...$fakerConfig);
$fakeValue = $this->databasePlatform->quoteStringLiteral($fakeValue);
}
$columnName = $metadata->getColumnName($fieldName);
$sets[] = "$columnName = $fakeValue";
} }
return sprintf($this->updateSQLTemplate, return sprintf($this->updateSQLTemplate,
$tableName, $metadata->getTableName(),
implode(', ', $sets), implode(', ', $sets),
$record['id'] $record['id']
); );
} }
public function genUpdateForRestaurer(array $record, array $mapping, ClassMetadata $metadata): string protected function generateFakeValue(array $fakerConfig, string $entity, string $field): string
{ {
$tableName = $metadata->getTableName(); $method = array_shift($fakerConfig);
if ($method === 'null') {
return 'NULL';
}
$sets = []; $fakeValue = $this->faker->$method(...$fakerConfig);
Assert::scalar($fakeValue, "La méthode '$method' spécifiée pour l'attribut '$field' de l'entité '$entity' " .
"retourne une valeur non supportée : %s");
return $this->databasePlatform->quoteStringLiteral($fakeValue);
}
public function genUpdateForRestaurer(array $record, array $mapping, ClassMetadata $metadata): ?string
{
Assert::notEmpty($mapping, "Mapping vide !");
$sets = [];
foreach ($mapping as $fieldName => $fakerConfig) { foreach ($mapping as $fieldName => $fakerConfig) {
$value = $this->databasePlatform->quoteStringLiteral($record[$fieldName]); $value = $this->databasePlatform->quoteStringLiteral($record[$fieldName]);
$columnName = $metadata->getColumnName($fieldName); $columnName = $metadata->getColumnName($fieldName);
...@@ -153,7 +143,7 @@ class DbService ...@@ -153,7 +143,7 @@ class DbService
} }
return sprintf($this->updateSQLTemplate, return sprintf($this->updateSQLTemplate,
$tableName, $metadata->getTableName(),
implode(', ', $sets), implode(', ', $sets),
$record['id'] $record['id']
); );
......
<?php <?php
/** /**
* @var \UnicaenApp\Service\SQL\RunSQLResult $result * @var \UnicaenSql\Service\SQL\RunSQLResult $result
*/ */
$this->headTitle("Lancement"); $this->headTitle("Lancement");
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment