> */ final class ExpungeCommand implements CommandInterface { private readonly ?SequenceSet $sequenceSet; public function __construct(FetchTarget|string|SequenceSet|null $target = null) { if ($target === null) { $this->sequenceSet = null; return; } $resolvedTarget = match (true) { $target instanceof FetchTarget => $target, $target instanceof SequenceSet => FetchTarget::sequence($target), is_string($target) => FetchTarget::sequence($target), default => null, }; if ($resolvedTarget === null || $resolvedTarget->identifierMode() !== IdentifierMode::Uid) { throw new ImapException('Targeted EXPUNGE requires a UID target.'); } $this->sequenceSet = $resolvedTarget->sequenceSet(); } public function name(): string { return 'EXPUNGE'; } public function allowedStates(): array { return [SessionState::Selected]; } public function encode(string $tag, SessionContext $context): RequestFrame { unset($tag); if ($this->sequenceSet === null) { unset($context); return new RequestFrame('EXPUNGE'); } if (!$context->hasCapability('UIDPLUS')) { throw new ImapException('UID EXPUNGE requires the IMAP UIDPLUS capability.'); } return new RequestFrame(sprintf( 'UID EXPUNGE %s', $this->sequenceSet->toCommand(), )); } public function handle(ResponseStream $responses, SessionContext $context): array { if ($context->selectedMailbox() === null) { throw new ImapException('EXPUNGE requires a selected mailbox.'); } $expunged = []; foreach ($responses as $response) { if ($response instanceof UntaggedResponse && preg_match('/^\*\s+(\d+)\s+EXPUNGE$/i', $response->raw(), $matches) === 1) { $expunged[] = (int) $matches[1]; continue; } if ($response instanceof TaggedResponse) { if (!$response->isOk()) { throw new ImapException($this->sequenceSet === null ? 'EXPUNGE failed: ' . $response->text() : 'UID EXPUNGE failed: ' . $response->text()); } return $expunged; } } throw new ImapException($this->sequenceSet === null ? 'EXPUNGE did not receive a tagged completion response.' : 'UID EXPUNGE did not receive a tagged completion response.'); } }