addOption('overwrite', null, InputOption::VALUE_NONE, 'Overwrite users that already have a profile image set') ->addOption('style', null, InputOption::VALUE_OPTIONAL, 'DiceBear style to use for avatars', 'thumbs') ->addOption('seed-prefix', null, InputOption::VALUE_OPTIONAL, 'Prefix added to the avatar seed for variety', 'musicratings'); } protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); $overwrite = (bool) $input->getOption('overwrite'); $style = (string) $input->getOption('style'); $seedPrefix = (string) $input->getOption('seed-prefix'); $users = $this->userRepository->findAll(); if ($users === []) { $io->warning('No users found.'); return Command::FAILURE; } $updated = 0; foreach ($users as $user) { if (!$user instanceof User) { continue; } if (!$overwrite && $user->getProfileImagePath()) { // Respect existing uploads unless the operator explicitly allows clobbering them. continue; } $user->setProfileImagePath($this->buildAvatarUrl($user, $style, $seedPrefix)); $updated++; } if ($updated === 0) { $io->info('No avatars needed updating.'); return Command::SUCCESS; } $this->entityManager->flush(); $io->success(sprintf('Assigned avatars to %d user(s).', $updated)); return Command::SUCCESS; } private function buildAvatarUrl(User $user, string $style, string $seedPrefix): string { // Use a stable identifier (display name when present, email as fallback) // so the same user is always mapped to the same avatar for a given prefix. $identifier = trim((string) ($user->getDisplayName() ?? $user->getEmail())); // Combine prefix, identifier, and primary key into a deterministic hash // and trim it to a shorter seed value accepted by DiceBear. $seed = substr(hash('sha256', $seedPrefix . '|' . strtolower($identifier) . '|' . (string) $user->getId()), 0, 32); return sprintf('https://api.dicebear.com/7.x/%s/svg?seed=%s', rawurlencode($style), $seed); } }