Commit 853555a8 authored by Bertrand Gauthier's avatar Bertrand Gauthier
Browse files

Refactorisation (nouveau service dédié à la génération avec Faker)

parent f6a753e1
Loading
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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' => [
@@ -156,6 +158,7 @@ return [
        'factories' => [
            DbService::class => DbServiceFactory::class,
            AnonymService::class => AnonymServiceFactory::class,
            GeneratorService::class => GeneratorServiceFactory::class,
        ],
    ],
    'controllers' => [
+1 −1
Original line number Diff line number Diff line
@@ -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)
+1 −1
Original line number Diff line number Diff line
@@ -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);
            }
+6 −30
Original line number Diff line number Diff line
@@ -2,7 +2,6 @@

namespace UnicaenDbAnonym\Service;

use Doctrine\ORM\Mapping\ClassMetadata;
use Generator;
use UnicaenSql\Service\SQL\RunSQLResult;
use Webmozart\Assert\Assert;
@@ -17,6 +16,7 @@ class AnonymService

    protected array $entitiesConfig = [];
    protected array $outputConfig = [];
    protected array $processedEntities = [];

    public function setConfig(array $config): self
    {
@@ -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(
@@ -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,
@@ -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
+49 −46
Original line number Diff line number Diff line
@@ -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;
@@ -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
@@ -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 {
@@ -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