Skip to content
Snippets Groups Projects
Select Git revision
  • master
  • unicaen_authentification
  • release_3.1.0
  • 3.x
  • 2.x
  • 4.0.0
  • 3.1.0
  • 2.0.1
  • 3.0.0
  • 2.0.0
  • 1.0.0
11 results

DbService.php

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    DbService.php 8.16 KiB
    <?php
    
    namespace UnicaenDbAnonym\Service;
    
    use Doctrine\DBAL\ConnectionException;
    use Doctrine\DBAL\Exception;
    use Doctrine\DBAL\Platforms\AbstractPlatform;
    use Doctrine\ORM\EntityManager;
    use Doctrine\ORM\Mapping\ClassMetadata;
    use Laminas\Log\Logger;
    use Laminas\Log\Writer\Stream;
    use RuntimeException;
    use Throwable;
    use UnicaenSql\Service\SQL\RunSQLProcess;
    use UnicaenSql\Service\SQL\RunSQLResult;
    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 = %s ;';
        protected array $processedEntities = [];
    
        /**
         * @param \Doctrine\ORM\EntityManager $entityManager
         * @throws \Doctrine\DBAL\Exception
         */
        public function setEntityManager(EntityManager $entityManager): void
        {
            $this->entityManager = $entityManager;
            $this->databasePlatform = $this->entityManager->getConnection()->getDatabasePlatform();
        }
    
        public function isBddAnonymisee(): bool
        {
            $sqlTemplate = "select value from unicaen_db_anonym where key = '%s'";
    
            $sql = sprintf($sqlTemplate, self::METADATA_KEY_ANONYMISEE);
    
            try {
                /** @var string|false $result */
                $result = $this->entityManager->getConnection()->executeQuery($sql)->fetchOne();
            } catch (Throwable $e) {
                throw new RuntimeException(
                    "Erreur lors de l'interrogation du témoin d'anonymisation dans la table des metadata", null, $e);
            }
    
            if ($result === false) {
                throw new RuntimeException(sprintf(
                    "Le témoin d'anonymisation '%s' est introuvable dans la table des metadata",
                    self::METADATA_KEY_ANONYMISEE
                ));
            }
    
            return $result === '1';
        }
    
        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 {
                $this->entityManager->getConnection()->executeQuery($sql);
            } catch (Exception $e) {
                throw new RuntimeException(
                    "Erreur lors de la modification du témoin d'anonymisation dans la table des metadata", null, $e);
            }
        }
    
        public function fetchEntityRecords(string $entityName, array $fields, array $except): array
        {
            $qb = $this->entityManager->createQueryBuilder()
                ->select("partial t.{id," . implode(',', $fields) . "}")
                ->from($entityName, 't');
    
            foreach ($except as $field => $values) {
                $qb->andWhere($qb->expr()->notIn('t.' . $field, (array)$values));
            }
    
            return $qb->getQuery()->getArrayResult();
        }
    
        public function getEntityClassMetadata(string $entityName): ClassMetadata
        {
            return $this->entityManager->getClassMetadata($entityName);
        }
    
        public function normalizeMapping(string $entityName, array $mapping): array
        {
            $metadata = $this->getEntityClassMetadata($entityName);
    
            // 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);
        }
    
        /**
         * @param string $entityName
         * @param array $records
         * @param array $mapping Exemple :
         * ```
         * [
         *   'nomUsuel' => 'lastName', // càd `$faker->lastName()`
         *   'nomPatronymique' => 'null', // càd mise à NULL.
         *   'leitmotiv' => ['words', 3, true], // càd `$faker->words(3, true)`
         *   'leitmotivUnique' => [
         *      'name' => 'words',
         *      'unique' => true,
         *      'params' => [3, true],
         *    ], // càd `$faker->unique()->words(3, true)`
         *    'civilite' => [
         *       'name' => 'randomElement',
         *       'params' => [Individu::CIVILITE_MME, Individu::CIVILITE_M],
         *    ], // càd `$faker->randomElement([Individu::CIVILITE_MME, Individu::CIVILITE_M])`
         *    'prenom1' => [
         *       'name' => 'firstName',
         *       'params' => [null],
         *       'context' => true,
         *    ] // càd `$faker->firstName(null, $context)` // Cf. {@see \UnicaenDbAnonym\Provider\fr_FR\PersonExtension}
         * ]
         * ```
         * @return array[]
         */
        public function generateSqlLines(string $entityName, array $records, array $mapping): array
        {
            $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;
            }
    
            $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);
            }
    
            return [$migrateSqlLines, $restoreSqlLines];
        }
    
        private function genUpdateForAnonymiser(array $record, array $mapping, ClassMetadata $metadata): ?string
        {
            $entity = $metadata->getName();
    
            $sets = [];
            $context = [];
            $this->generatorService->seed($record['id']);
            foreach ($mapping as $name => $fakerConfig) {
                $fakeValue = $this->generatorService->generateFakeValue((array)$fakerConfig, $context);
                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);
                $context[$name] = $fakeValue; // contexte = valeurs générées jusqu'à présent
            }
    
            return sprintf($this->updateSQLTemplate,
                $metadata->getTableName(),
                implode(', ', $sets),
                $record['id']
            );
        }
    
        private function genUpdateForRestaurer(array $record, array $mapping, ClassMetadata $metadata): ?string
        {
            Assert::notEmpty($mapping, "Mapping vide !");
    
            $sets = [];
            foreach ($mapping as $fieldName => $fakerConfig) {
                $value = $this->databasePlatform->quoteStringLiteral($record[$fieldName]);
                $columnName = $metadata->getColumnName($fieldName);
                $sets[] = "$columnName = $value";
            }
    
            return sprintf($this->updateSQLTemplate,
                $metadata->getTableName(),
                implode(', ', $sets),
                $record['id']
            );
        }
    
        /**
         * @throws \Doctrine\DBAL\ConnectionException
         * @throws \Doctrine\DBAL\Exception
         */
        public function lancerScript(string $scriptPath): RunSQLResult
        {
            Assert::readable($scriptPath, "Le script '$scriptPath' n'existe pas ou n'est pas lisible");
    
            $conn = $this->entityManager->getConnection();
            $conn->beginTransaction();
    
            $p = new RunSQLProcess();
            $p->setConnection($conn);
            $p->setScriptPath($scriptPath);
            $p->setQueriesSplitPattern("#;\n#m"); // point virgule puis un retour
            $p->setLogFilePath($scriptPath . '.log');
            $p->setLogger((new Logger())->addWriter(new Stream('php://stdout')));
            $result = $p->executeScript();
    
            try {
                $conn->commit();
            } catch (ConnectionException $e) {
                $conn->rollBack();
                throw $e;
            }
    
            return $result;
        }
    }