output->insertOne([ $user->getEmail(), $user->getUsername(), $user->getId(), ]); } protected function concatenateViolations(ConstraintViolationListInterface $list) { $str = []; foreach ($list as $e) { /* @var $e \Symfony\Component\Validator\ConstraintViolationInterface */ $str[] = $e->getMessage(); } return \implode(';', $str); } protected function configure() { $this ->setHelp("Import users from a csv file. Users are added to centers contained in the file. Headers are used to detect columns. Adding to multiple centers can be done by using a `grouping centers` file, which will group multiple centers into a signle alias, used in 'centers' column.") ->addArgument('csvfile', InputArgument::REQUIRED, 'Path to the csv file. Columns are: `username`, `email`, `center` (can contain alias), `permission group`') ->addOption('grouping-centers', null, InputOption::VALUE_OPTIONAL, 'Path to a csv file to aggregate multiple centers into a single alias') ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Do not commit the changes') ->addOption('csv-dump', null, InputOption::VALUE_REQUIRED, 'A path to dump a summary of the created file'); } protected function createOrGetGroupCenter(Center $center, PermissionsGroup $pg): GroupCenter { if (\array_key_exists($center->getId(), $this->groupCenters)) { if (\array_key_exists($pg->getId(), $this->groupCenters[$center->getId()])) { return $this->groupCenters[$center->getId()][$pg->getId()]; } } $repository = $this->em->getRepository(GroupCenter::class); $groupCenter = $repository->findOneBy([ 'center' => $center, 'permissionsGroup' => $pg, ]); if (null === $groupCenter) { $groupCenter = new GroupCenter(); $groupCenter ->setCenter($center) ->setPermissionsGroup($pg); $this->em->persist($groupCenter); } $this->groupCenters[$center->getId()][$pg->getId()] = $groupCenter; return $groupCenter; } protected function createUser($offset, $data) { $user = new User(); $user ->setEmail(\trim((string) $data['email'])) ->setUsername(\trim((string) $data['username'])) ->setEnabled(true) ->setPassword($this->passwordEncoder->hashPassword( $user, \bin2hex(\random_bytes(32)) )); $errors = $this->validator->validate($user); if ($errors->count() > 0) { $errorMessages = $this->concatenateViolations($errors); $this->tempOutput->writeln(sprintf('%d errors found with user with username "%s" at line %d', $errors->count(), $data['username'], $offset)); $this->tempOutput->writeln($errorMessages); throw new \RuntimeException('Found errors while creating an user. Watch messages in command output'); } $pgs = $this->getPermissionGroup($data['permission group']); $centers = $this->getCenters($data['center']); foreach ($pgs as $pg) { foreach ($centers as $center) { $groupcenter = $this->createOrGetGroupCenter($center, $pg); if (false === $user->getGroupCenters()->contains($groupcenter)) { $user->addGroupCenter($groupcenter); } } } if ($this->doChanges) { $this->em->persist($user); $this->em->flush(); } $this->logger->notice('Create user', [ 'username' => $user->getUsername(), 'id' => $user->getId(), 'nb_of_groupCenters' => $user->getGroupCenters()->count(), ]); return $user; } protected function doesUserExists($data) { if ($this->userRepository->countByUsernameOrEmail($data['username']) > 0) { return true; } if ($this->userRepository->countByUsernameOrEmail($data['email']) > 0) { return true; } return false; } protected function execute(InputInterface $input, OutputInterface $output): int { $this->tempOutput = $output; $this->tempInput = $input; if ($input->getOption('dry-run')) { $this->doChanges = false; } $this->prepareWriter(); if ($input->hasOption('grouping-centers')) { $this->prepareGroupingCenters(); } try { $this->loadUsers(); } catch (\Exception $e) { throw $e; } return Command::SUCCESS; } /** * return a list of centers matching the name of alias. * * If the name match one center, this center is returned in an array. * * If the name match an alias, the centers corresponding to the alias are * returned in an array. * * If the center is not found or alias is not created, a new center is created * and suggested to user * * @param string $name the name of the center or the alias regrouping center * * @return Center[] */ protected function getCenters($name) { // sanitize $name = \trim($name); if (\array_key_exists($name, $this->centers)) { return $this->centers[$name]; } // search for a center with given name $center = $this->em->getRepository(Center::class) ->findOneByName($name); if ($center instanceof Center) { $this->centers[$name] = [$center]; return $this->centers[$name]; } // suggest and create $center = (new Center()) ->setName($name); $this->tempOutput->writeln("Center with name \"{$name}\" not found."); $qFormatter = $this->getHelper('question'); $question = new ConfirmationQuestion("Create a center with name \"{$name}\" ?", true); if ($qFormatter->ask($this->tempInput, $this->tempOutput, $question)) { $this->centers[$name] = [$center]; $errors = $this->validator->validate($center); if ($errors->count() > 0) { $errorMessages = $this->concatenateViolations($errors); $this->tempOutput->writeln(sprintf('%d errors found with center with name "%s"', $errors->count(), $name)); $this->tempOutput->writeln($errorMessages); throw new \RuntimeException('Found errors while creating one center. Watch messages in command output'); } $this->em->persist($center); return $this->centers[$name]; } return null; } protected function getPermissionGroup($alias) { if (\array_key_exists($alias, $this->permissionGroups)) { return $this->permissionGroups[$alias]; } $permissionGroupsByName = []; foreach ( $this->em->getRepository(PermissionsGroup::class) ->findAll() as $permissionGroup ) { $permissionGroupsByName[$permissionGroup->getName()] = $permissionGroup; } if (0 === \count($permissionGroupsByName)) { throw new \RuntimeException('no permission groups found. Create them before importing users'); } $question = new ChoiceQuestion( "To which permission groups associate with \"{$alias}\" ?", \array_keys($permissionGroupsByName) ); $question ->setMultiselect(true) ->setAutocompleterValues(\array_keys($permissionGroupsByName)) ->setNormalizer(static function ($value) { if (null === $value) { return ''; } return \trim((string) $value); }); $helper = $this->getHelper('question'); $keys = $helper->ask($this->tempInput, $this->tempOutput, $question); $this->tempOutput->writeln('You have chosen '.\implode(', ', $keys)); if ( $helper->ask( $this->tempInput, $this->tempOutput, new ConfirmationQuestion('Are you sure ?', true) ) ) { foreach ($keys as $key) { $this->permissionGroups[$alias][] = $permissionGroupsByName[$key]; } return $this->permissionGroups[$alias]; } $this->logger->error('Error while responding to a a question'); $this->tempOutput->writeln('Ok, I accept, but I do not know what to do. Please try again.'); throw new \RuntimeException('Error while responding to a question'); } protected function loadUsers() { $reader = Reader::createFromPath($this->tempInput->getArgument('csvfile')); $reader->setHeaderOffset(0); foreach ($reader->getRecords() as $line => $r) { $this->logger->debug('starting handling new line', [ 'line' => $line, ]); if ($this->doesUserExists($r)) { $this->tempOutput->writeln(sprintf("User with username '%s' already " .'exists, skipping', $r['username'])); $this->logger->info('One user already exists, skipping creation', [ 'username_in_file' => $r['username'], 'email_in_file' => $r['email'], 'line' => $line, ]); continue; } $user = $this->createUser($line, $r); $this->appendUserToFile($user); } } protected function prepareGroupingCenters() { $reader = Reader::createFromPath($this->tempInput->getOption('grouping-centers')); $reader->setHeaderOffset(0); foreach ($reader->getRecords() as $r) { $this->centers[$r['alias']] = \array_merge( $this->centers[$r['alias']] ?? [], $this->getCenters( $r['center'] ) ); } } protected function prepareWriter() { $this->output = $output = Writer::createFromPath($this->tempInput ->getOption('csv-dump'), 'a+'); $output->insertOne([ 'email', 'username', 'id', ]); } }