DbToolsBundle, enfin un outil pour utiliser légalement nos données de prod en local
« Nul n’est censé ignorer la loi », commençons donc par une piqûre de rappel :
Il est illégal d’utiliser les données personnelles de vos utilisateurs ailleurs que sur la prod. Plus précisément, le RGPD1 indique que :
Les données à caractère personnel doivent être :
- traitées de manière licite, loyale et transparente au regard de la personne concernée (principes de licéité, de loyauté, et de transparence);
- collectées pour des finalités déterminées, explicites et légitimes, et ne pas être traitées ultérieurement d’une manière incompatible avec ces finalités; le traitement ultérieur à des fins archivistiques dans l’intérêt public, à des fins de recherche scientifique ou historique ou à des fins statistiques n’est pas considéré, conformément à l’article 89, paragraphe 1, comme incompatible avec les finalités initiales (limitation des finalités);
- […]
- traitées de façon à garantir une sécurité appropriée des données à caractère personnel, y compris la protection contre le traitement non autorisé ou illicite et contre la perte, la destruction ou les dégâts d’origine accidentelle, à l’aide de mesures techniques ou organisationnelles appropriées (intégrité et confidentialité);
En résumé, l’utilisation de données personnelles dans le cadre du développement d’une application ne rentre pas dans le cadre légal fixé par le RGPD.
Section intitulée qu-avons-nous-le-droit-de-faireQu’avons-nous le droit de faire ?
Quotidiennement, nous avons besoin de données pour nourrir nos applications en local afin de tester notre code, corriger des bugs, faire évoluer des fonctionnalités, etc. Ces données « de travail » peuvent être générées grâce à des outils comme Foundry, que nous vous recommandons chaudement. Mais parfois les fixtures ne suffisent pas, par manque de temps pour les maintenir, parce que la complexité du modèle de données augmente, et qu’il devient chronophage de tenir les fixtures à jour, ou pour plein d’autres raisons (coucou le legacy).
Il devient alors tentant d’utiliser les meilleures données qui soient, c’est-à-dire celles de la production. Mais comme nous l’avons vu quelques lignes plus haut, c’est illégal (du moins au sein de l’Union Européenne).
Heureusement, nous avons une alliée pour faire face à ce dilemme, la CNIL. En plus d’être l’organisation qui s’assure que le numérique soit au service des citoyens et qu’il ne porte pas atteinte aux droits de l’Homme, la CNIL édite également des guides à destination des professionnels afin de les aider à respecter la loi, qu’elle soit Française ou Européenne.
Ainsi, dans le Guide pratique RGPD2 il est recommandé :
De ne pas utiliser des données personnelles réelles pour les phases de développement et de test. > Des jeux fictifs doivent être utilisés autant que possible.
Par ailleurs, la CNIL édite également un Guide RGPD pour l’équipe de développement, que nous vous invitons à consulter.
Maintenant que nous connaissons nos droits, imaginons le cas suivant :
Une application Symfony en production depuis quelques années, avec un modèle de données assez conséquent, dont l’équipe de développement cherche à optimiser son temps et notamment à se passer de la maintenance des fixtures, devenue trop chronophage et complexe.
L’idéal, dans ce cas d’exemple, serait de pouvoir fournir, à chaque développeur, une copie de la base de données de production dans laquelle toutes les données personnelles des utilisateurs ont été anonymisées. Voyons comment mettre en place une telle solution !
Section intitulée comment-la-respecter-sans-s-arracher-les-cheveuxComment la respecter sans s’arracher les cheveux ?
Toute la difficulté réside dans le mot « Anonymisation ». Faire une copie d’une base de données est relativement simple, peu importe que le projet utilise MySQL, PostgreSQL, etc. Mais anonymiser les données de cette copie est une tâche spécifique à chaque base de données.
Nous devons nous poser plusieurs questions en amont :
- Qu’est ce qu’une donnée personnelle ?
- Est-ce que certaines données indirectement liées aux utilisateurs pourraient permettre de retrouver leur identité (factures, fichiers envoyés par les utilisateurs comme des photos de profil, code promotion utilisé sur une boutique en ligne, etc).
NB : Bien sûr, comme nous ne maîtrisons pas TOUT Internet, le RGPD nous laisse une marge de manœuvre quant à la possibilité de ré-identifier une personne à partir d’une donnée issue d’un autre système d’information (par exemple, un code promotionnel unique émis par un partenaire et utiliser sur notre boutique en ligne pourrait être un cas à la marge).
La première étape est donc de lister toutes les colonnes de notre base de données qui peuvent s’apparenter à de la donnée personnelle.
Ensuite, nous devons respecter un ordre précis d’opérations à effectuer pour garantir que nous n’aurons jamais accès aux données personnelles des utilisateurs.
Section intitulée outillage-techniqueOutillage technique
Jusqu’à présent, nous faisions ce genre de choses au cas par cas selon les besoins spécifiques de chaque projet et/ou client. Mais depuis peu il existe un outil écrit en PHP qui va grandement faciliter la vie des développeurs Symfony : DbToolsBundle.
Ce bundle Symfony nous offre un ensemble de commandes afin de sauvegarder, anonymiser et restaurer des bases de données. Ces commandes reposent sur le composant Command de Symfony, nous sommes donc en terrain connu.
Faisons ensemble un petit tour du propriétaire avant de voir comment le mettre en place dans un cas plus concret.
Section intitulée configurationConfiguration
Après quelques lignes de configuration pour indiquer au bundle quel moteur de base de données vous utilisez, nous allons devoir indiquer au bundle quelles propriétés de nos entités nous voulons anonymiser :
#[ORM\Column(length: 180, unique: true)]
#[Anonymize(type: 'email')]
private ?string $emailAddress = null;
Cette étape se fait assez simplement avec des Attributs à placer sur les propriétés de vos entités Doctrine !
La documentation est très bien faite, elle liste tous les Attributs disponibles. Il existe même un système de « packs » localisés pour anonymiser vos données dans la bonne locale selon vos utilisateurs.
Pour une utilisation avancée, il est même possible de créer vos propres « Anonymizers ».
Section intitulée commandes-disponiblesCommandes disponibles
La première commande bin/console db-tools:backup
va tirer partie des binaires fournis avec MariaDB, MySQL, PostgreSQL ou SQLite pour exporter un dump. Nous nous retrouvons donc avec un fichier .dump
(nommons ce dump « D1 ») dans le dossier var/db_tools/default/{Y}/{m}
avec Y
et m
correspondant respectivement à l’année et au mois courant.
La commande suivante bin/console db-tools:anonymize [PATH]
va effectuer une série d’actions pour anonymiser le dump précédemment créé. ⚠️ Attention, cette commande charger le dump dans la base de données courante de l’environnement sur lequel elle est exécutée pour effectuer l’anonymisation, puis exporter le résultat dans un fichier .dump portant le même nom. Pour en savoir plus et maîtriser vos actions, consultez la documentation.
Pour finir, la dernière commande bin/console db-tools:restore [FILENAME]
permet de charger un dump dans notre base de données locale. Ici, elle nous servira à charger le dump fraîchement anonymisé.
Section intitulée dans-la-vraie-vieDans la vraie vie
Les personnes de Makina Corpus, l’entreprise derrière le bundle DbToolsBundle fournissent dans la documentation un excellent schéma animé pour comprendre un des workflows idéal pour utiliser des données anonymisées en local à partir d’une base de données de production.
Dans ce schéma, l’environnement « tampon », appelé « Preprod », va régulièrement (par exemple une fois par jour) accéder à un backup de la base de données de production, l’anonymiser, et stocker sur le disque le dump prêt à l’emploi pour les développeurs, qui pourront l’importer en local grâce à la commande bin/console db-tools:restore
.
La partie « accès à un backup » dépend entièrement de votre projet. Si votre environnement de production dispose de backups, il faudra permettre à l’environnement intermédiaire d’y accéder. S’ils sont chiffrés, il faudra pouvoir les déchiffrer. Si jamais vous n’avez pas de backups, (demandez nous de vous aider à en mettre en place via notre page de contact) vous pouvez également configurer un accès dédié en lecture seule de votre preprod à votre base de données de production. Ce n’est pas ce que nous recommandons, car cet accès peut être détourné de son usage primaire.
Il existe également une solution que nous trouvons plus élégante. L’environnement intermédiaire peut également être notre service d’intégration continue !
En effet, sur la plupart des services de CI, il est désormais possible de faire exécuter à intervalles réguliers des actions.
Prenons le cas d’une application ayant des backups de base de données configurés pour être chiffrés et stockés sur un Object Storage distant. Nous pouvons stocker les informations d’accès à cet Object Storage ainsi que la clé de déchiffrement dans les « secrets » de notre service de CI. Enfin, quelques lignes de YAML devraient suffire pour configurer une action récurrente qui récupère le dernier backup en date, exécute la commande bin/console db-tools:anonymize [PATH]
avec ce backup, et stocke le fichier .dump anonymisé dans les artefacts de notre CI afin de le rendre disponible aux développeurs.
Quelques points d’attention doivent néanmoins être pris en compte lors de la mise en place de ce processus :
- L’environnement de votre CI devra disposer du binaire correspondant à votre base de données (
mariadb
,mysql
,pg_restore
, etc), tout comme vos environnements de développement locaux pour la restauration ; - Pensez à inclure vos secrets de CI dans la rotation de vos clés de chiffrement de vos backups de production le cas échéant.
L’avantage principal de ce workflow est qu’il permet, en plus de mettre à disposition des développeurs un dump qualitatif, de tester l’intégrité de vos backups de production !
Section intitulée bonus-une-tache-castorBonus, une tâche Castor 🦫
Les commandes fournies par DbToolsBundle sont très efficaces et assez simples à utiliser. Cependant, elles impliquent, pour deux d’entre elles, de connaître le chemin vers le backup à anonymiser, ou à restaurer.
Pour simplifier ça, nous vous proposons trois tâches qui tirent parti de notre nouveau task runner, Castor !
const LATEST_BACKUP_CACHE_KEY = 'latest-backup';
const LATEST_ANONYMIZED_BACKUP_CACHE_KEY = 'latest-anonymized-backup';
#[AsTask(description: 'Backup database')]
function backup(): void
{
run('bin/console db-tools:backup');
cache(LATEST_BACKUP_CACHE_KEY, function () {
$files = finder()
->in(__DIR__.'/var/db_tools/default/'.date('Y').'/'.date('m'))
->sortByNames()
->reverseSorting()
->files();
foreach ($files as $file) {
return $file;
}
throw new \RuntimeException('No backup file found');
});
}
#[AsTask(description: 'Anonymize database')]
function anonymize(
#[AsArgument(description: 'Path to the raw backup file, if not provided, the latest created backup will be used')]
string $path = null,
): void {
$path ??= cache(
LATEST_BACKUP_CACHE_KEY,
fn() => throw new \RuntimeException('No backup file found, please provide a path to the raw backup file.')
);
run('bin/console db-tools:anonymize --no-interaction '.$path);
cache(LATEST_ANONYMIZED_BACKUP_CACHE_KEY, fn() => $path);
}
#[AsTask(description: 'Get last anonymized backup path')]
function last_anonymized_backup(): void
{
$path = cache(
LATEST_ANONYMIZED_BACKUP_CACHE_KEY,
fn() => throw new \RuntimeException('No backup file found, please backup your database and anonymize it first')
);
io()->text($path);
}
#[AsTask(description: 'Restore anonymized database')]
function restore(
#[AsArgument(description: 'Path to the anonymized backup file, if not provided, the latest anonymized backup will be used')]
string $filename = null,
): void {
$filename ??= cache(
LATEST_BACKUP_CACHE_KEY,
fn() => throw new \RuntimeException('No backup file found, please provide a path to the anonymized backup file.')
);
run('bin/console db-tools:restore --yes-i-am-sure-of-what-i-am-doing --force --filename '.$filename);
get_cache()->delete(LATEST_ANONYMIZED_BACKUP_CACHE_KEY);
get_cache()->delete(LATEST_BACKUP_CACHE_KEY);
}
Ces tâches sont fournies à but informatif, libre à vous de vous en inspirer pour améliorer votre workflow et simplifier la vie de vos développeurs !
Section intitulée sources-et-liensSources et liens
Commentaires et discussions
Améliorer la DX de vos Fixtures PHP
Les fixtures sont utilisées pour charger des données définies par les développeurs dans une base de données. Elles sont très utiles en environnement de développement car elles permettent d’avoir une application avec plusieurs jeux de données qui correspondent à ce qu’il se passe…
Lire la suite de l’article Améliorer la DX de vos Fixtures PHP
Comment tester fonctionnellement un projet legacy
Travailler sur un projet fraîchement démarré, c’est hype ! Mais beaucoup d’entre nous n’ont pas cette chance. Au travers d’une mission, j’ai dû mettre en place un système d’intégration continue sur un projet considéré par certains comme « legacy ». Le projet comporte plusieurs…
Lire la suite de l’article Comment tester fonctionnellement un projet legacy
Nos articles sur le même sujet
Nos formations sur ce sujet
Notre expertise est aussi disponible sous forme de formations professionnelles !

Symfony avancée
Découvrez les fonctionnalités et concepts avancés de Symfony
Ces clients ont profité de notre expertise
JoliCode a formé l’équipe de développement d’Evaneos aux bonnes pratiques pour l’écriture de tests unitaires efficaces et utiles. Nous en avons également profité pour mettre en place une plateforme d’intégration continue pour accompagner l’évolution de la plateforme.
Nous avons épaulé Adrenaline Hunter, juste avant le lancement public de ses offres, sur des problématiques liées à la performance de leur application développée avec Symfony. Nous avons également mis en place un système de workflow des commandes de séjours afin que toutes les actions avec leurs différents partenaires soient réparties avec fiabilité…
LOOK Cycle bénéficie désormais d’une nouvelle plateforme eCommerce disponible sur 70 pays et 5 langues. La base technique modulaire de Sylius permet de répondre aux exigences de LOOK Cycle en terme de catalogue, produits, tunnel d’achat, commandes, expéditions, gestion des clients et des expéditions.