option('artwork')) ->map(static fn (mixed $id): int => (int) $id) ->filter(static fn (int $id): bool => $id > 0) ->values() ->all(); $force = (bool) $this->option('force'); $dryRun = (bool) $this->option('dry-run'); $queue = (bool) $this->option('queue'); $limit = max(0, (int) $this->option('limit')); $all = (bool) $this->option('all'); $explicitOnlyFeatured = (bool) $this->option('only-featured'); $missingOnly = $force ? false : ((bool) $this->option('missing-only') || ($artworkIds === [] && ! $all)); if ($all && $explicitOnlyFeatured) { $this->error('Use either --all or --only-featured, not both.'); return self::INVALID; } if ($queue && $dryRun) { $this->error('Use either --queue or --dry-run, not both.'); return self::INVALID; } $onlyFeatured = $artworkIds === [] && ! $all; if ($explicitOnlyFeatured) { $onlyFeatured = true; } $processed = 0; $queued = 0; $generatedVariants = 0; $skipped = 0; $failed = 0; foreach ($this->candidateArtworks($artworkIds, $onlyFeatured) as $artwork) { if ($limit > 0 && $processed >= $limit) { break; } $processed++; $plan = $this->generator->plan($artwork, $force); $targetVariants = (array) ($plan['target_variants'] ?? []); if ($missingOnly && ! $force && $targetVariants === []) { $skipped++; continue; } if ($dryRun) { $this->line(sprintf( '[dry-run] artwork=%d variants=%s', (int) $artwork->id, $targetVariants === [] ? 'none' : implode(',', $targetVariants), )); if ($targetVariants === []) { $skipped++; } else { $generatedVariants += count($targetVariants); } continue; } if ($queue) { GenerateFeaturedArtworkThumbnailsJob::dispatch((int) $artwork->id, $force); $queued++; continue; } try { $result = $this->generator->generate($artwork, $force); $generatedVariants += (int) ($result['generated'] ?? 0); $skipped += count((array) ($result['target_variants'] ?? [])) === 0 ? 1 : 0; if (($result['failed'] ?? []) !== []) { $failed++; $this->warn(sprintf( 'Artwork %d failed for variants: %s', (int) $artwork->id, implode(', ', array_keys((array) $result['failed'])), )); } } catch (\Throwable $exception) { $failed++; $this->warn(sprintf('Artwork %d failed: %s', (int) $artwork->id, $exception->getMessage())); } } $mode = $dryRun ? 'dry-run' : ($queue ? 'queued' : 'generated'); $this->info(sprintf( 'Featured artwork thumbnail %s complete: processed=%d queued=%d generated_variants=%d skipped=%d failed=%d', $mode, $processed, $queued, $generatedVariants, $skipped, $failed, )); return $failed > 0 ? self::FAILURE : self::SUCCESS; } /** * @param list $artworkIds * @return LazyCollection */ private function candidateArtworks(array $artworkIds, bool $onlyFeatured): LazyCollection { if ($artworkIds !== []) { return Artwork::query() ->withTrashed() ->whereIn('id', $artworkIds) ->whereNotNull('hash') ->where('hash', '!=', '') ->whereNotNull('file_ext') ->where('file_ext', '!=', '') ->orderByDesc('id') ->cursor(); } $query = $onlyFeatured ? $this->selector->querySelectedArtworks() : Artwork::query() ->select('artworks.*') ->withTrashed() ->whereNotNull('hash') ->where('hash', '!=', '') ->whereNotNull('file_ext') ->where('file_ext', '!=', ''); return $this->orderedCursor($query); } /** * @return LazyCollection */ private function orderedCursor(Builder $query): LazyCollection { return $query ->orderByDesc('artworks.id') ->cursor(); } }