|null */ private ?array $allCodesCache = null; public function __construct( private ?string $codesPath = null, ) {} /** * @return list */ public function allCodes(): array { if ($this->allCodesCache !== null) { return $this->allCodesCache; } $path = $this->codesPath ?? resource_path('codes/codes.txt'); if (! is_readable($path)) { throw new \RuntimeException("Coupon codes file is not readable: {$path}"); } $lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); if ($lines === false) { throw new \RuntimeException("Unable to read coupon codes file: {$path}"); } $codes = array_values(array_filter(array_map(trim(...), $lines))); if ($codes === []) { throw new \RuntimeException("Coupon codes file is empty: {$path}"); } return $this->allCodesCache = $codes; } /** * @return list */ public function usedCodes(): array { return Coupon::query()->pluck('code')->all(); } /** * @return list */ public function availableCodes(): array { return array_values(array_diff($this->allCodes(), $this->usedCodes())); } public function hasAvailable(): bool { return $this->availableCodes() !== []; } public function pickRandom(): ?string { $available = $this->availableCodes(); if ($available === []) { return null; } return $available[array_rand($available)]; } }