If you often need to work with a copy of a production database on your local environment, you might have to anonymize the contents of all personal data due to restrictions of the GDPR. I often ship my Laravel projects with an artisan-command called “CleanupPersonalData”, in which I add some logic to purge all privacy-sensitive contents from the database.
Looking for a similar solution? Notice that this always requires manual implementation, since you need to review which data/columns in the database are personal, and which not.
Let take for example a simple ‘User’ model. After downloading a copy of your production database, you have a copy of all users, probably including columns as first_name, last_name, email and password.
namespace App\Console\Commands; use App\Models\User; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; class CleanupPersonalData extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'cleanup:gdpr {aggressive?} {force?}'; /** * The console command description. * * @var string */ protected $description = 'Cleanup Personal data for local environments'; /** * Create a new command instance. * * @return void */ public function __construct() { parent::__construct(); } /** * Execute the console command. * * @return mixed */ public function handle() { if ('production' == config('app.env')) { abort(403, 'This command should only be run in local/testing environments!'); exit; } // do we want to force overwrite contents, or check if this was anonymized before? $force = false; if ('true' == $this->argument('force')) { $force = true; } // prepend this string to anonymized data (also to check if anonymized before) $anonymizeString = 'anonymized: '; // using Faker class to generate new dummy content $faker = \Faker\Factory::create('nl_NL'); // START MODEL 1 (USER) // specify a main table-column you will be checking for if the data was already anonymized $mainColumn = 'lastname'; // make sure we fetch trashed items as well (if soft-deletes is enabled) $items = User::withTrashed(); // if not forced, check if anonymized before if (!$force) { $items ->where(function ($query) use ($mainColumn, $anonymizeString) { $query->where($mainColumn, 'NOT LIKE', DB::raw($anonymizeString) . '%'); $query->orWhereNull($mainColumn); }); } // get all items as a Collection $items = $items->get(); $this->output->writeln(sprintf('%s users', $items->count())); // make it easy for us to login to test-users (do this once, since hashing consumes a lot of memory / cpu) $pass = bcrypt('123456'); foreach ($items as $item) { if ($force || strpos($item->$mainColumn, $anonymizeString) === false) { $item->$mainColumn = $anonymizeString.$faker->lastName; $item->firstname = $faker->firstName; $item->email = $faker->safeEmail; // to make sure we won't send any emails to real addresses $item->password = $pass; $item->save(); } } // END MODEL 1 (USER) } }
Repeat the above for multiple Models and you no longer have to worry about data-leaks. Make a copy of your production-database, import it into the local environments, run the command
php artisan cleanup:gdpr
and make sure to delete the original backup-file.