|null */ private ?array $cache = null; /** * @param ConfigRepositoryInterface $configRepository */ public function __construct( private readonly ConfigRepositoryInterface $configRepository, ) {} /** * 채널이 발송 준비 상태인지 확인합니다. * * @param string $channelId 채널 식별자 * @return bool */ public function isReady(string $channelId): bool { return $this->check($channelId)['ready']; } /** * 채널별 준비 상태와 사유를 반환합니다. * * @param string $channelId 채널 식별자 * @return array{ready: bool, reason: string|null} */ public function check(string $channelId): array { if ($this->cache !== null && isset($this->cache[$channelId])) { return $this->cache[$channelId]; } try { $result = $this->performCheck($channelId); } catch (\Throwable $e) { // 안전성: 체크 자체가 실패하면 ready=true (발송 시도 허용) Log::warning('ChannelReadinessService: 체크 실패, 발송 허용', [ 'channel' => $channelId, 'error' => $e->getMessage(), ]); $result = ['ready' => true, 'reason' => null]; } $this->cache ??= []; $this->cache[$channelId] = $result; return $result; } /** * 여러 채널의 준비 상태를 일괄 반환합니다. * * @param array $channelIds 채널 ID 배열 * @return array */ public function checkAll(array $channelIds): array { $results = []; foreach ($channelIds as $id) { $results[$id] = $this->check($id); } return $results; } /** * 채널별 체크 로직을 실행합니다. * * 코어 채널은 내부 메서드로, 플러그인 채널은 훅으로 처리합니다. * * @param string $channelId 채널 식별자 * @return array{ready: bool, reason: string|null} */ private function performCheck(string $channelId): array { // 코어 채널 체크 $result = match ($channelId) { 'mail' => $this->checkMail(), 'database' => $this->checkDatabase(), default => ['ready' => true, 'reason' => null], }; // 플러그인 확장: 커스텀 채널 체커 적용 return HookManager::applyFilters( 'core.notification.channel_readiness', $result, $channelId ); } /** * 메일 채널 준비 상태를 검증합니다. * * @return array{ready: bool, reason: string|null} */ private function checkMail(): array { $mailSettings = $this->configRepository->getCategory('mail'); $mailer = $mailSettings['mailer'] ?? 'smtp'; // from_address는 모든 mailer에 공통 필수 $fromAddress = $mailSettings['from_address'] ?? ''; if (empty($fromAddress) || $fromAddress === 'noreply@example.com') { return ['ready' => false, 'reason' => 'notification.readiness.mail_from_address_not_configured']; } return match ($mailer) { 'smtp' => $this->checkSmtp($mailSettings), 'mailgun' => $this->checkMailgun($mailSettings), 'ses' => $this->checkSes($mailSettings), 'log', 'array' => ['ready' => true, 'reason' => null], default => ['ready' => true, 'reason' => null], }; } /** * SMTP mailer 필수 설정을 검증합니다. * * @param array $settings 메일 설정 * @return array{ready: bool, reason: string|null} */ private function checkSmtp(array $settings): array { if (empty($settings['host'])) { return ['ready' => false, 'reason' => 'notification.readiness.mail_smtp_host_empty']; } if (empty($settings['port'])) { return ['ready' => false, 'reason' => 'notification.readiness.mail_smtp_port_empty']; } return ['ready' => true, 'reason' => null]; } /** * Mailgun mailer 필수 설정을 검증합니다. * * @param array $settings 메일 설정 * @return array{ready: bool, reason: string|null} */ private function checkMailgun(array $settings): array { if (empty($settings['mailgun_domain'])) { return ['ready' => false, 'reason' => 'notification.readiness.mail_mailgun_domain_empty']; } if (empty($settings['mailgun_secret'])) { return ['ready' => false, 'reason' => 'notification.readiness.mail_mailgun_secret_empty']; } return ['ready' => true, 'reason' => null]; } /** * SES mailer 필수 설정을 검증합니다. * * @param array $settings 메일 설정 * @return array{ready: bool, reason: string|null} */ private function checkSes(array $settings): array { if (empty($settings['ses_key'])) { return ['ready' => false, 'reason' => 'notification.readiness.mail_ses_key_empty']; } if (empty($settings['ses_secret'])) { return ['ready' => false, 'reason' => 'notification.readiness.mail_ses_secret_empty']; } return ['ready' => true, 'reason' => null]; } /** * database 채널 준비 상태를 검증합니다. * * @return array{ready: bool, reason: string|null} */ private function checkDatabase(): array { try { if (! Schema::hasTable('notifications')) { return ['ready' => false, 'reason' => 'notification.readiness.database_table_missing']; } } catch (\Throwable $e) { // DB 접속 실패: 안전하게 ready=true (발송 시도 허용) return ['ready' => true, 'reason' => null]; } return ['ready' => true, 'reason' => null]; } }