$data Additional structured data * @param array $channels Delivery channels * @param NotificationPriority $priority Delivery priority * @param NotificationStatus $status Current status * @param Timestamp|null $sentAt Delivery timestamp * @param Timestamp|null $readAt Read timestamp * @param string|null $actionUrl Optional action URL * @param string|null $actionLabel Optional action button label */ public function __construct( public NotificationId $id, public string $recipientId, public NotificationTypeInterface $type, public string $title, public string $body, public Timestamp $createdAt, public array $data = [], public array $channels = [], public NotificationPriority $priority = NotificationPriority::NORMAL, public NotificationStatus $status = NotificationStatus::PENDING, public ?Timestamp $sentAt = null, public ?Timestamp $readAt = null, public ?string $actionUrl = null, public ?string $actionLabel = null ) { if (empty($recipientId)) { throw new \InvalidArgumentException('Recipient ID cannot be empty'); } if (empty($title)) { throw new \InvalidArgumentException('Title cannot be empty'); } if (empty($body)) { throw new \InvalidArgumentException('Body cannot be empty'); } if (empty($channels)) { throw new \InvalidArgumentException('At least one delivery channel is required'); } } public static function create( string $recipientId, NotificationTypeInterface $type, string $title, string $body, NotificationChannel ...$channels ): self { return new self( id: NotificationId::generate(), recipientId: $recipientId, type: $type, title: $title, body: $body, createdAt: Timestamp::now(), data: [], channels: $channels, priority: NotificationPriority::NORMAL, status: NotificationStatus::PENDING ); } public function withData(array $data): self { return new self( id: $this->id, recipientId: $this->recipientId, type: $this->type, title: $this->title, body: $this->body, createdAt: $this->createdAt, data: [...$this->data, ...$data], channels: $this->channels, priority: $this->priority, status: $this->status, sentAt: $this->sentAt, readAt: $this->readAt, actionUrl: $this->actionUrl, actionLabel: $this->actionLabel ); } public function withPriority(NotificationPriority $priority): self { return new self( id: $this->id, recipientId: $this->recipientId, type: $this->type, title: $this->title, body: $this->body, createdAt: $this->createdAt, data: $this->data, channels: $this->channels, priority: $priority, status: $this->status, sentAt: $this->sentAt, readAt: $this->readAt, actionUrl: $this->actionUrl, actionLabel: $this->actionLabel ); } public function withAction(string $url, string $label): self { return new self( id: $this->id, recipientId: $this->recipientId, type: $this->type, title: $this->title, body: $this->body, createdAt: $this->createdAt, data: $this->data, channels: $this->channels, priority: $this->priority, status: $this->status, sentAt: $this->sentAt, readAt: $this->readAt, actionUrl: $url, actionLabel: $label ); } public function markAsSent(): self { return new self( id: $this->id, recipientId: $this->recipientId, type: $this->type, title: $this->title, body: $this->body, createdAt: $this->createdAt, data: $this->data, channels: $this->channels, priority: $this->priority, status: NotificationStatus::SENT, sentAt: Timestamp::now(), readAt: $this->readAt, actionUrl: $this->actionUrl, actionLabel: $this->actionLabel ); } public function markAsDelivered(): self { return new self( id: $this->id, recipientId: $this->recipientId, type: $this->type, title: $this->title, body: $this->body, createdAt: $this->createdAt, data: $this->data, channels: $this->channels, priority: $this->priority, status: NotificationStatus::DELIVERED, sentAt: $this->sentAt ?? Timestamp::now(), readAt: $this->readAt, actionUrl: $this->actionUrl, actionLabel: $this->actionLabel ); } public function markAsRead(): self { return new self( id: $this->id, recipientId: $this->recipientId, type: $this->type, title: $this->title, body: $this->body, createdAt: $this->createdAt, data: $this->data, channels: $this->channels, priority: $this->priority, status: NotificationStatus::READ, sentAt: $this->sentAt, readAt: Timestamp::now(), actionUrl: $this->actionUrl, actionLabel: $this->actionLabel ); } public function markAsFailed(): self { return new self( id: $this->id, recipientId: $this->recipientId, type: $this->type, title: $this->title, body: $this->body, createdAt: $this->createdAt, data: $this->data, channels: $this->channels, priority: $this->priority, status: NotificationStatus::FAILED, sentAt: $this->sentAt, readAt: $this->readAt, actionUrl: $this->actionUrl, actionLabel: $this->actionLabel ); } public function isRead(): bool { return $this->status === NotificationStatus::READ; } public function hasAction(): bool { return $this->actionUrl !== null; } public function supportsChannel(NotificationChannel $channel): bool { foreach ($this->channels as $supportedChannel) { if ($supportedChannel === $channel) { return true; } } return false; } public function toArray(): array { return [ 'id' => $this->id->toString(), 'recipient_id' => $this->recipientId, 'type' => $this->type->toString(), 'title' => $this->title, 'body' => $this->body, 'data' => $this->data, 'channels' => array_map(fn ($c) => $c->value, $this->channels), 'priority' => $this->priority->value, 'status' => $this->status->value, 'created_at' => $this->createdAt->format('Y-m-d H:i:s'), 'sent_at' => $this->sentAt?->format('Y-m-d H:i:s'), 'read_at' => $this->readAt?->format('Y-m-d H:i:s'), 'action_url' => $this->actionUrl, 'action_label' => $this->actionLabel, ]; } }