96 lines
3.5 KiB
PHP
96 lines
3.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Command;
|
|
|
|
use App\Entity\User;
|
|
use App\Repository\UserRepository;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Symfony\Component\Console\Attribute\AsCommand;
|
|
use Symfony\Component\Console\Command\Command;
|
|
use Symfony\Component\Console\Input\InputInterface;
|
|
use Symfony\Component\Console\Input\InputOption;
|
|
use Symfony\Component\Console\Output\OutputInterface;
|
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
|
|
|
#[AsCommand(
|
|
name: 'app:seed-user-avatars',
|
|
description: 'Assign generated profile images to existing users.'
|
|
)]
|
|
/**
|
|
* Seeds or refreshes user profile images using the DiceBear avatar API.
|
|
*
|
|
* - Skips users that already have an image unless --overwrite is provided.
|
|
* - Builds deterministic avatar URLs based on user identity and an optional seed prefix.
|
|
* - Does not download or cache the avatars locally; URLs are stored directly.
|
|
*/
|
|
class SeedUserAvatarsCommand extends Command
|
|
{
|
|
public function __construct(
|
|
private readonly UserRepository $userRepository,
|
|
private readonly EntityManagerInterface $entityManager,
|
|
) {
|
|
parent::__construct();
|
|
}
|
|
|
|
protected function configure(): void
|
|
{
|
|
$this
|
|
->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);
|
|
}
|
|
}
|
|
|
|
|