addOption('max-per-album', null, InputOption::VALUE_OPTIONAL, 'Maximum reviews per album', 10) ->addOption('min-per-album', null, InputOption::VALUE_OPTIONAL, 'Minimum reviews per selected album', 1) ->addOption('cover-percent', null, InputOption::VALUE_OPTIONAL, 'Percent of albums that should receive reviews (0-100)', 60) ->addOption('only-empty', null, InputOption::VALUE_NONE, 'Only seed albums that currently have no reviews'); } protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); $albums = $this->albumRepository->findAll(); $users = $this->userRepository->findAll(); if ($albums === [] || $users === []) { $io->warning('Need at least one album and one user to seed reviews.'); return Command::FAILURE; } $minPerAlbum = max(0, (int) $input->getOption('min-per-album')); $maxPerAlbum = max($minPerAlbum, (int) $input->getOption('max-per-album')); $coverPercent = max(0, min(100, (int) $input->getOption('cover-percent'))); $selectedAlbums = $this->selectAlbums($albums, $coverPercent); $onlyEmpty = (bool) $input->getOption('only-empty'); $created = 0; $processedAlbums = 0; foreach ($selectedAlbums as $album) { if ($onlyEmpty && $this->albumHasReviews($album)) { continue; } $targetReviews = random_int($minPerAlbum, max($minPerAlbum, $maxPerAlbum)); $created += $this->seedForAlbum($album, $users, $targetReviews); $processedAlbums++; } $this->entityManager->flush(); if ($created === 0) { $io->warning('No reviews were created. Try relaxing the filters or ensure there are albums without reviews.'); return Command::SUCCESS; } $io->success(sprintf('Created %d demo reviews across %d albums.', $created, max($processedAlbums, 1))); return Command::SUCCESS; } /** * @param list $albums * @return list */ private function selectAlbums(array $albums, int $coverPercent): array { if ($coverPercent >= 100) { return $albums; } $selected = []; foreach ($albums as $album) { if (random_int(1, 100) <= $coverPercent) { $selected[] = $album; } } return $selected === [] ? [$albums[array_rand($albums)]] : $selected; } /** * @param list $users */ private function seedForAlbum(Album $album, array $users, int $targetReviews): int { $created = 0; $existingAuthors = $this->fetchExistingAuthors($album); $availableUsers = array_filter($users, fn(User $user) => !isset($existingAuthors[$user->getId() ?? -1])); if ($availableUsers === []) { return 0; } $targetReviews = min($targetReviews, count($availableUsers)); shuffle($availableUsers); $selectedUsers = array_slice($availableUsers, 0, $targetReviews); foreach ($selectedUsers as $user) { $review = new Review(); $review->setAlbum($album); $review->setAuthor($user); $review->setRating(random_int(4, 10)); $review->setTitle($this->generateTitle()); $review->setContent($this->generateContent($album)); $this->entityManager->persist($review); $created++; } return $created; } /** * @return array */ private function fetchExistingAuthors(Album $album): array { $qb = $this->entityManager->createQueryBuilder() ->select('IDENTITY(r.author) AS authorId') ->from(Review::class, 'r') ->where('r.album = :album') ->setParameter('album', $album); $rows = $qb->getQuery()->getScalarResult(); $out = []; foreach ($rows as $row) { $out[(int) $row['authorId']] = true; } return $out; } private function albumHasReviews(Album $album): bool { $count = (int) $this->entityManager->createQueryBuilder() ->select('COUNT(r.id)') ->from(Review::class, 'r') ->where('r.album = :album') ->setParameter('album', $album) ->getQuery() ->getSingleScalarResult(); return $count > 0; } private function generateTitle(): string { $subject = self::SUBJECTS[random_int(0, count(self::SUBJECTS) - 1)]; $verb = self::VERBS[random_int(0, count(self::VERBS) - 1)]; return sprintf('%s %s the vibe', $subject, $verb); } private function generateContent(Album $album): string { $qualifier = self::QUALIFIERS[random_int(0, count(self::QUALIFIERS) - 1)]; return sprintf( 'Listening to "%s" feels like %s. %s %s %s, and by the end it lingers far longer than expected.', $album->getName(), $qualifier, self::SUBJECTS[random_int(0, count(self::SUBJECTS) - 1)], self::VERBS[random_int(0, count(self::VERBS) - 1)], self::QUALIFIERS[random_int(0, count(self::QUALIFIERS) - 1)] ); } }