Loading config/module.config.php +3 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,8 @@ use UnicaenDbAnonym\Service\AnonymService; use UnicaenDbAnonym\Service\AnonymServiceFactory; use UnicaenDbAnonym\Service\DbService; use UnicaenDbAnonym\Service\DbServiceFactory; use UnicaenDbAnonym\Service\GeneratorService; use UnicaenDbAnonym\Service\GeneratorServiceFactory; return [ 'unicaen-db-anonym' => [ Loading Loading @@ -156,6 +158,7 @@ return [ 'factories' => [ DbService::class => DbServiceFactory::class, AnonymService::class => AnonymServiceFactory::class, GeneratorService::class => GeneratorServiceFactory::class, ], ], 'controllers' => [ Loading src/UnicaenDbAnonym/Controller/ConsoleController.php +1 −1 Original line number Diff line number Diff line Loading @@ -20,7 +20,7 @@ class ConsoleController extends AbstractConsoleController $anonymisationScriptPath = $this->anonymService->getAnonymisationScriptPath(); $restaurationScriptPath = $this->anonymService->getRestaurationScriptPath(); try { $gen = $this->anonymService->generer(); $gen = $this->anonymService->genererScripts(); foreach ($gen as ['entity' => $entity, 'fields' => $fields, 'count' => $count]) { $this->console->writeLine( sprintf("- Entite %s (%s) : %d lignes traitees", $entity, implode(', ', $fields), $count) Loading src/UnicaenDbAnonym/Controller/IndexController.php +1 −1 Original line number Diff line number Diff line Loading @@ -37,7 +37,7 @@ class IndexController extends AbstractController $messages = []; try { $gen = $this->anonymService->generer(); $gen = $this->anonymService->genererScripts(); foreach ($gen as ['table' => $table, 'fields' => $fields, 'count' => $count]) { $messages[] = sprintf("- Table %s (colonnes %s) : %d lignes", $table, implode(', ', $fields), $count); } Loading src/UnicaenDbAnonym/Service/AnonymService.php +6 −30 Original line number Diff line number Diff line Loading @@ -2,7 +2,6 @@ namespace UnicaenDbAnonym\Service; use Doctrine\ORM\Mapping\ClassMetadata; use Generator; use UnicaenSql\Service\SQL\RunSQLResult; use Webmozart\Assert\Assert; Loading @@ -17,6 +16,7 @@ class AnonymService protected array $entitiesConfig = []; protected array $outputConfig = []; protected array $processedEntities = []; public function setConfig(array $config): self { Loading @@ -36,7 +36,7 @@ class AnonymService return $this->outputConfig['restauration']; } public function generer(): Generator public function genererScripts(): Generator { if ($this->dbService->isBddAnonymisee()) { throw new InvalidArgumentException( Loading @@ -59,31 +59,18 @@ class AnonymService $rf = fopen($restaurationFilePath, 'w'); foreach ($this->entitiesConfig as $entityName => $entityConfig) { Assert::keyExists($entityConfig, $k = 'mapping', "La config de l'entité '$entityName' doit posséder la clé '$k'"); $metadata = $this->dbService->getEntityClassMetadata($entityName); $mapping = $this->normalizeMapping($metadata, $entityConfig['mapping']); $except = $entityConfig['except'] ?? []; $mapping = $this->dbService->normalizeMapping($entityName, $entityConfig['mapping']); $fields = array_keys($mapping); $except = $entityConfig['except'] ?? []; $records = $this->dbService->fetchEntityRecords($entityName, $fields, $except); $migrateSqlLines = []; $restaurationSqlLines = []; foreach ($records as $record) { // on anonymise pas les colonnes dont la valeur est null $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); } [$migrateSqlLines, $restoreSqlLines] = $this->dbService->generateSqlLines($entityName, $records, $mapping); fputs($mf, implode(PHP_EOL, $migrateSqlLines) . PHP_EOL); fputs($rf, implode(PHP_EOL, $restaurationSqlLines) . PHP_EOL); fputs($rf, implode(PHP_EOL, $restoreSqlLines) . PHP_EOL); yield [ 'entity' => $entityName, Loading @@ -98,17 +85,6 @@ class AnonymService 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\Exception Loading src/UnicaenDbAnonym/Service/DbService.php +49 −46 Original line number Diff line number Diff line Loading @@ -7,11 +7,8 @@ use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\ClassMetadata; use Faker\Factory; use Faker\Generator; use Laminas\Log\Logger; use Laminas\Log\Writer\Stream; use Locale; use RuntimeException; use Throwable; use UnicaenSql\Service\SQL\RunSQLProcess; Loading @@ -20,17 +17,14 @@ use Webmozart\Assert\Assert; class DbService { use GeneratorServiceAwareTrait; const METADATA_KEY_ANONYMISEE = 'BDD_ANONYMISEE'; protected EntityManager $entityManager; protected AbstractPlatform $databasePlatform; protected string $updateSQLTemplate = 'update %s set %s where id = %d ;'; protected Generator $faker; public function __construct() { $this->faker = Factory::create(Locale::getDefault()); } protected string $updateSQLTemplate = 'update %s set %s where id = %s ;'; protected array $processedEntities = []; /** * @param \Doctrine\ORM\EntityManager $entityManager Loading Loading @@ -69,7 +63,6 @@ class DbService public function setBddAnonymisee(bool $bddAnonymisee): void { $sqlTemplate = "update unicaen_db_anonym set value = '%d' where key = '%s'"; $sql = sprintf($sqlTemplate, $bddAnonymisee ? '1' : '0', self::METADATA_KEY_ANONYMISEE); try { Loading Loading @@ -98,56 +91,66 @@ class DbService return $this->entityManager->getClassMetadata($entityName); } public function genUpdateForAnonymiser(array $record, array $mapping, ClassMetadata $metadata): ?string public function normalizeMapping(string $entityName, array $mapping): array { Assert::notEmpty($mapping, "Mapping vide !"); $sets = []; foreach ($mapping as $name => $fakerConfig) { $fakeValue = $this->generateFakeValue((array)$fakerConfig, $metadata->getName(), $name); $columnName = $metadata->getColumnName($name); $sets[] = $columnName . ' = ' . $fakeValue; } $metadata = $this->getEntityClassMetadata($entityName); return sprintf($this->updateSQLTemplate, $metadata->getTableName(), implode(', ', $sets), $record['id'] // 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); } protected function generateFakeValue(array $fakerConfig, string $entity, string $field): string public function generateSqlLines(string $entityName, array $records, array $mapping): array { // normalisation if (!array_key_exists('name', $fakerConfig)) { $method = array_shift($fakerConfig); $fakerConfig = ['name' => $method, 'params' => array_values($fakerConfig)]; } if (!array_key_exists('unique', $fakerConfig)) { $fakerConfig['unique'] = false; $metadata = $this->getEntityClassMetadata($entityName); // Reset du modificateur unique() à chaque nouvelle entité rencontrée (cf. http://fakerphp.org/#modifiers), // sachant que l'utilisation de unique() dépend de ce qui est spécifié dans la config. if (!in_array($entityName, $this->processedEntities)) { $this->generatorService->resetUniqueModifier(); $this->processedEntities[] = $entityName; } $method = $fakerConfig['name']; if ($method === 'null') { return 'NULL'; $migrateSqlLines = []; $restoreSqlLines = []; foreach ($records as $record) { // on n'anonymise pas les colonnes dont la valeur est null $preparedRecord = array_filter($record, fn($v) => $v !== null); $preparedMapping = array_intersect_key($mapping, $preparedRecord); if (count($preparedMapping) === 0) { continue; // toutes les valeurs sont null, next ! } $migrateSqlLines[] = $this->genUpdateForAnonymiser($preparedRecord, $preparedMapping, $metadata); $restoreSqlLines[] = $this->genUpdateForRestaurer($preparedRecord, $preparedMapping, $metadata); } $params = $fakerConfig['params'] ?? []; $unique = (bool) $fakerConfig['unique']; $faker = $this->faker; if ($unique) { $faker = $this->faker->unique(); return [$migrateSqlLines, $restoreSqlLines]; } $fakeValue = $faker->$method(...$params); private function genUpdateForAnonymiser(array $record, array $mapping, ClassMetadata $metadata): ?string { $entity = $metadata->getName(); 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"); $sets = []; $this->generatorService->seed($record['id']); foreach ($mapping as $name => $fakerConfig) { $fakeValue = $this->generatorService->generateFakeValue((array)$fakerConfig); Assert::scalar($fakeValue, "Le mapping spécifiée pour l'attribut '$name' de l'entité '$entity' produit une valeur non supportée : %s"); $sets[] = $metadata->getColumnName($name) . ' = ' . $this->databasePlatform->quoteStringLiteral($fakeValue); } return $this->databasePlatform->quoteStringLiteral($fakeValue); return sprintf($this->updateSQLTemplate, $metadata->getTableName(), implode(', ', $sets), $record['id'] ); } public function genUpdateForRestaurer(array $record, array $mapping, ClassMetadata $metadata): ?string private function genUpdateForRestaurer(array $record, array $mapping, ClassMetadata $metadata): ?string { Assert::notEmpty($mapping, "Mapping vide !"); Loading Loading
config/module.config.php +3 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,8 @@ use UnicaenDbAnonym\Service\AnonymService; use UnicaenDbAnonym\Service\AnonymServiceFactory; use UnicaenDbAnonym\Service\DbService; use UnicaenDbAnonym\Service\DbServiceFactory; use UnicaenDbAnonym\Service\GeneratorService; use UnicaenDbAnonym\Service\GeneratorServiceFactory; return [ 'unicaen-db-anonym' => [ Loading Loading @@ -156,6 +158,7 @@ return [ 'factories' => [ DbService::class => DbServiceFactory::class, AnonymService::class => AnonymServiceFactory::class, GeneratorService::class => GeneratorServiceFactory::class, ], ], 'controllers' => [ Loading
src/UnicaenDbAnonym/Controller/ConsoleController.php +1 −1 Original line number Diff line number Diff line Loading @@ -20,7 +20,7 @@ class ConsoleController extends AbstractConsoleController $anonymisationScriptPath = $this->anonymService->getAnonymisationScriptPath(); $restaurationScriptPath = $this->anonymService->getRestaurationScriptPath(); try { $gen = $this->anonymService->generer(); $gen = $this->anonymService->genererScripts(); foreach ($gen as ['entity' => $entity, 'fields' => $fields, 'count' => $count]) { $this->console->writeLine( sprintf("- Entite %s (%s) : %d lignes traitees", $entity, implode(', ', $fields), $count) Loading
src/UnicaenDbAnonym/Controller/IndexController.php +1 −1 Original line number Diff line number Diff line Loading @@ -37,7 +37,7 @@ class IndexController extends AbstractController $messages = []; try { $gen = $this->anonymService->generer(); $gen = $this->anonymService->genererScripts(); foreach ($gen as ['table' => $table, 'fields' => $fields, 'count' => $count]) { $messages[] = sprintf("- Table %s (colonnes %s) : %d lignes", $table, implode(', ', $fields), $count); } Loading
src/UnicaenDbAnonym/Service/AnonymService.php +6 −30 Original line number Diff line number Diff line Loading @@ -2,7 +2,6 @@ namespace UnicaenDbAnonym\Service; use Doctrine\ORM\Mapping\ClassMetadata; use Generator; use UnicaenSql\Service\SQL\RunSQLResult; use Webmozart\Assert\Assert; Loading @@ -17,6 +16,7 @@ class AnonymService protected array $entitiesConfig = []; protected array $outputConfig = []; protected array $processedEntities = []; public function setConfig(array $config): self { Loading @@ -36,7 +36,7 @@ class AnonymService return $this->outputConfig['restauration']; } public function generer(): Generator public function genererScripts(): Generator { if ($this->dbService->isBddAnonymisee()) { throw new InvalidArgumentException( Loading @@ -59,31 +59,18 @@ class AnonymService $rf = fopen($restaurationFilePath, 'w'); foreach ($this->entitiesConfig as $entityName => $entityConfig) { Assert::keyExists($entityConfig, $k = 'mapping', "La config de l'entité '$entityName' doit posséder la clé '$k'"); $metadata = $this->dbService->getEntityClassMetadata($entityName); $mapping = $this->normalizeMapping($metadata, $entityConfig['mapping']); $except = $entityConfig['except'] ?? []; $mapping = $this->dbService->normalizeMapping($entityName, $entityConfig['mapping']); $fields = array_keys($mapping); $except = $entityConfig['except'] ?? []; $records = $this->dbService->fetchEntityRecords($entityName, $fields, $except); $migrateSqlLines = []; $restaurationSqlLines = []; foreach ($records as $record) { // on anonymise pas les colonnes dont la valeur est null $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); } [$migrateSqlLines, $restoreSqlLines] = $this->dbService->generateSqlLines($entityName, $records, $mapping); fputs($mf, implode(PHP_EOL, $migrateSqlLines) . PHP_EOL); fputs($rf, implode(PHP_EOL, $restaurationSqlLines) . PHP_EOL); fputs($rf, implode(PHP_EOL, $restoreSqlLines) . PHP_EOL); yield [ 'entity' => $entityName, Loading @@ -98,17 +85,6 @@ class AnonymService 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\Exception Loading
src/UnicaenDbAnonym/Service/DbService.php +49 −46 Original line number Diff line number Diff line Loading @@ -7,11 +7,8 @@ use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\ClassMetadata; use Faker\Factory; use Faker\Generator; use Laminas\Log\Logger; use Laminas\Log\Writer\Stream; use Locale; use RuntimeException; use Throwable; use UnicaenSql\Service\SQL\RunSQLProcess; Loading @@ -20,17 +17,14 @@ use Webmozart\Assert\Assert; class DbService { use GeneratorServiceAwareTrait; const METADATA_KEY_ANONYMISEE = 'BDD_ANONYMISEE'; protected EntityManager $entityManager; protected AbstractPlatform $databasePlatform; protected string $updateSQLTemplate = 'update %s set %s where id = %d ;'; protected Generator $faker; public function __construct() { $this->faker = Factory::create(Locale::getDefault()); } protected string $updateSQLTemplate = 'update %s set %s where id = %s ;'; protected array $processedEntities = []; /** * @param \Doctrine\ORM\EntityManager $entityManager Loading Loading @@ -69,7 +63,6 @@ class DbService public function setBddAnonymisee(bool $bddAnonymisee): void { $sqlTemplate = "update unicaen_db_anonym set value = '%d' where key = '%s'"; $sql = sprintf($sqlTemplate, $bddAnonymisee ? '1' : '0', self::METADATA_KEY_ANONYMISEE); try { Loading Loading @@ -98,56 +91,66 @@ class DbService return $this->entityManager->getClassMetadata($entityName); } public function genUpdateForAnonymiser(array $record, array $mapping, ClassMetadata $metadata): ?string public function normalizeMapping(string $entityName, array $mapping): array { Assert::notEmpty($mapping, "Mapping vide !"); $sets = []; foreach ($mapping as $name => $fakerConfig) { $fakeValue = $this->generateFakeValue((array)$fakerConfig, $metadata->getName(), $name); $columnName = $metadata->getColumnName($name); $sets[] = $columnName . ' = ' . $fakeValue; } $metadata = $this->getEntityClassMetadata($entityName); return sprintf($this->updateSQLTemplate, $metadata->getTableName(), implode(', ', $sets), $record['id'] // 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); } protected function generateFakeValue(array $fakerConfig, string $entity, string $field): string public function generateSqlLines(string $entityName, array $records, array $mapping): array { // normalisation if (!array_key_exists('name', $fakerConfig)) { $method = array_shift($fakerConfig); $fakerConfig = ['name' => $method, 'params' => array_values($fakerConfig)]; } if (!array_key_exists('unique', $fakerConfig)) { $fakerConfig['unique'] = false; $metadata = $this->getEntityClassMetadata($entityName); // Reset du modificateur unique() à chaque nouvelle entité rencontrée (cf. http://fakerphp.org/#modifiers), // sachant que l'utilisation de unique() dépend de ce qui est spécifié dans la config. if (!in_array($entityName, $this->processedEntities)) { $this->generatorService->resetUniqueModifier(); $this->processedEntities[] = $entityName; } $method = $fakerConfig['name']; if ($method === 'null') { return 'NULL'; $migrateSqlLines = []; $restoreSqlLines = []; foreach ($records as $record) { // on n'anonymise pas les colonnes dont la valeur est null $preparedRecord = array_filter($record, fn($v) => $v !== null); $preparedMapping = array_intersect_key($mapping, $preparedRecord); if (count($preparedMapping) === 0) { continue; // toutes les valeurs sont null, next ! } $migrateSqlLines[] = $this->genUpdateForAnonymiser($preparedRecord, $preparedMapping, $metadata); $restoreSqlLines[] = $this->genUpdateForRestaurer($preparedRecord, $preparedMapping, $metadata); } $params = $fakerConfig['params'] ?? []; $unique = (bool) $fakerConfig['unique']; $faker = $this->faker; if ($unique) { $faker = $this->faker->unique(); return [$migrateSqlLines, $restoreSqlLines]; } $fakeValue = $faker->$method(...$params); private function genUpdateForAnonymiser(array $record, array $mapping, ClassMetadata $metadata): ?string { $entity = $metadata->getName(); 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"); $sets = []; $this->generatorService->seed($record['id']); foreach ($mapping as $name => $fakerConfig) { $fakeValue = $this->generatorService->generateFakeValue((array)$fakerConfig); Assert::scalar($fakeValue, "Le mapping spécifiée pour l'attribut '$name' de l'entité '$entity' produit une valeur non supportée : %s"); $sets[] = $metadata->getColumnName($name) . ' = ' . $this->databasePlatform->quoteStringLiteral($fakeValue); } return $this->databasePlatform->quoteStringLiteral($fakeValue); return sprintf($this->updateSQLTemplate, $metadata->getTableName(), implode(', ', $sets), $record['id'] ); } public function genUpdateForRestaurer(array $record, array $mapping, ClassMetadata $metadata): ?string private function genUpdateForRestaurer(array $record, array $mapping, ClassMetadata $metadata): ?string { Assert::notEmpty($mapping, "Mapping vide !"); Loading